blob: 6212642cada8bfb2383a3532d4e93c71c47b586e [file] [log] [blame]
// Copyright 2015 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 "core/paint/NinePieceImageGrid.h"
#include "core/style/ComputedStyle.h"
#include "core/style/NinePieceImage.h"
#include "platform/LengthFunctions.h"
#include "platform/geometry/FloatSize.h"
#include "platform/geometry/IntSize.h"
namespace blink {
static int computeEdgeWidth(const BorderImageLength& borderSlice,
int borderSide,
int imageSide,
int boxExtent) {
if (borderSlice.isNumber())
return borderSlice.number() * borderSide;
if (borderSlice.length().isAuto())
return imageSide;
return valueForLength(borderSlice.length(), LayoutUnit(boxExtent)).toInt();
}
static int computeEdgeSlice(const Length& slice, int maximum) {
return std::min<int>(maximum,
valueForLength(slice, LayoutUnit(maximum)).toInt());
}
NinePieceImageGrid::NinePieceImageGrid(const NinePieceImage& ninePieceImage,
IntSize imageSize,
IntRect borderImageArea,
const IntRectOutsets& borderWidths)
: m_borderImageArea(borderImageArea),
m_imageSize(imageSize),
m_horizontalTileRule((Image::TileRule)ninePieceImage.horizontalRule()),
m_verticalTileRule((Image::TileRule)ninePieceImage.verticalRule()),
m_fill(ninePieceImage.fill()) {
m_top.slice =
computeEdgeSlice(ninePieceImage.imageSlices().top(), imageSize.height());
m_right.slice =
computeEdgeSlice(ninePieceImage.imageSlices().right(), imageSize.width());
m_bottom.slice = computeEdgeSlice(ninePieceImage.imageSlices().bottom(),
imageSize.height());
m_left.slice =
computeEdgeSlice(ninePieceImage.imageSlices().left(), imageSize.width());
m_top.width =
computeEdgeWidth(ninePieceImage.borderSlices().top(), borderWidths.top(),
m_top.slice, borderImageArea.height());
m_right.width = computeEdgeWidth(ninePieceImage.borderSlices().right(),
borderWidths.right(), m_right.slice,
borderImageArea.width());
m_bottom.width = computeEdgeWidth(ninePieceImage.borderSlices().bottom(),
borderWidths.bottom(), m_bottom.slice,
borderImageArea.height());
m_left.width = computeEdgeWidth(ninePieceImage.borderSlices().left(),
borderWidths.left(), m_left.slice,
borderImageArea.width());
// The spec says: Given Lwidth as the width of the border image area, Lheight
// as its height, and Wside as the border image width offset for the side, let
// f = min(Lwidth/(Wleft+Wright), Lheight/(Wtop+Wbottom)). If f < 1, then all
// W are reduced by multiplying them by f.
int borderSideWidth = std::max(1, m_left.width + m_right.width);
int borderSideHeight = std::max(1, m_top.width + m_bottom.width);
float borderSideScaleFactor =
std::min((float)borderImageArea.width() / borderSideWidth,
(float)borderImageArea.height() / borderSideHeight);
if (borderSideScaleFactor < 1) {
m_top.width *= borderSideScaleFactor;
m_right.width *= borderSideScaleFactor;
m_bottom.width *= borderSideScaleFactor;
m_left.width *= borderSideScaleFactor;
}
}
// Given a rectangle, construct a subrectangle using offset, width and height.
// Negative offsets are relative to the extent of the given rectangle.
static FloatRect subrect(IntRect rect,
float offsetX,
float offsetY,
float width,
float height) {
float baseX = rect.x();
if (offsetX < 0)
baseX = rect.maxX();
float baseY = rect.y();
if (offsetY < 0)
baseY = rect.maxY();
return FloatRect(baseX + offsetX, baseY + offsetY, width, height);
}
static FloatRect subrect(IntSize size,
float offsetX,
float offsetY,
float width,
float height) {
return subrect(IntRect(IntPoint(), size), offsetX, offsetY, width, height);
}
static inline void setCornerPiece(
NinePieceImageGrid::NinePieceDrawInfo& drawInfo,
bool isDrawable,
const FloatRect& source,
const FloatRect& destination) {
drawInfo.isDrawable = isDrawable;
if (drawInfo.isDrawable) {
drawInfo.source = source;
drawInfo.destination = destination;
}
}
void NinePieceImageGrid::setDrawInfoCorner(NinePieceDrawInfo& drawInfo,
NinePiece piece) const {
switch (piece) {
case TopLeftPiece:
setCornerPiece(
drawInfo, m_top.isDrawable() && m_left.isDrawable(),
subrect(m_imageSize, 0, 0, m_left.slice, m_top.slice),
subrect(m_borderImageArea, 0, 0, m_left.width, m_top.width));
break;
case BottomLeftPiece:
setCornerPiece(drawInfo, m_bottom.isDrawable() && m_left.isDrawable(),
subrect(m_imageSize, 0, -m_bottom.slice, m_left.slice,
m_bottom.slice),
subrect(m_borderImageArea, 0, -m_bottom.width,
m_left.width, m_bottom.width));
break;
case TopRightPiece:
setCornerPiece(
drawInfo, m_top.isDrawable() && m_right.isDrawable(),
subrect(m_imageSize, -m_right.slice, 0, m_right.slice, m_top.slice),
subrect(m_borderImageArea, -m_right.width, 0, m_right.width,
m_top.width));
break;
case BottomRightPiece:
setCornerPiece(drawInfo, m_bottom.isDrawable() && m_right.isDrawable(),
subrect(m_imageSize, -m_right.slice, -m_bottom.slice,
m_right.slice, m_bottom.slice),
subrect(m_borderImageArea, -m_right.width, -m_bottom.width,
m_right.width, m_bottom.width));
break;
default:
ASSERT_NOT_REACHED();
break;
}
}
static inline void setHorizontalEdge(
NinePieceImageGrid::NinePieceDrawInfo& drawInfo,
const NinePieceImageGrid::Edge& edge,
const FloatRect& source,
const FloatRect& destination,
Image::TileRule tileRule) {
drawInfo.isDrawable = edge.isDrawable() && source.width() > 0;
if (drawInfo.isDrawable) {
drawInfo.source = source;
drawInfo.destination = destination;
drawInfo.tileScale = FloatSize(edge.scale(), edge.scale());
drawInfo.tileRule = {tileRule, Image::StretchTile};
}
}
static inline void setVerticalEdge(
NinePieceImageGrid::NinePieceDrawInfo& drawInfo,
const NinePieceImageGrid::Edge& edge,
const FloatRect& source,
const FloatRect& destination,
Image::TileRule tileRule) {
drawInfo.isDrawable = edge.isDrawable() && source.height() > 0;
if (drawInfo.isDrawable) {
drawInfo.source = source;
drawInfo.destination = destination;
drawInfo.tileScale = FloatSize(edge.scale(), edge.scale());
drawInfo.tileRule = {Image::StretchTile, tileRule};
}
}
void NinePieceImageGrid::setDrawInfoEdge(NinePieceDrawInfo& drawInfo,
NinePiece piece) const {
IntSize edgeSourceSize = m_imageSize - IntSize(m_left.slice + m_right.slice,
m_top.slice + m_bottom.slice);
IntSize edgeDestinationSize =
m_borderImageArea.size() -
IntSize(m_left.width + m_right.width, m_top.width + m_bottom.width);
switch (piece) {
case LeftPiece:
setVerticalEdge(drawInfo, m_left,
subrect(m_imageSize, 0, m_top.slice, m_left.slice,
edgeSourceSize.height()),
subrect(m_borderImageArea, 0, m_top.width, m_left.width,
edgeDestinationSize.height()),
m_verticalTileRule);
break;
case RightPiece:
setVerticalEdge(drawInfo, m_right,
subrect(m_imageSize, -m_right.slice, m_top.slice,
m_right.slice, edgeSourceSize.height()),
subrect(m_borderImageArea, -m_right.width, m_top.width,
m_right.width, edgeDestinationSize.height()),
m_verticalTileRule);
break;
case TopPiece:
setHorizontalEdge(drawInfo, m_top,
subrect(m_imageSize, m_left.slice, 0,
edgeSourceSize.width(), m_top.slice),
subrect(m_borderImageArea, m_left.width, 0,
edgeDestinationSize.width(), m_top.width),
m_horizontalTileRule);
break;
case BottomPiece:
setHorizontalEdge(
drawInfo, m_bottom,
subrect(m_imageSize, m_left.slice, -m_bottom.slice,
edgeSourceSize.width(), m_bottom.slice),
subrect(m_borderImageArea, m_left.width, -m_bottom.width,
edgeDestinationSize.width(), m_bottom.width),
m_horizontalTileRule);
break;
default:
ASSERT_NOT_REACHED();
break;
}
}
void NinePieceImageGrid::setDrawInfoMiddle(NinePieceDrawInfo& drawInfo) const {
IntSize sourceSize = m_imageSize - IntSize(m_left.slice + m_right.slice,
m_top.slice + m_bottom.slice);
IntSize destinationSize =
m_borderImageArea.size() -
IntSize(m_left.width + m_right.width, m_top.width + m_bottom.width);
drawInfo.isDrawable =
m_fill && !sourceSize.isEmpty() && !destinationSize.isEmpty();
if (!drawInfo.isDrawable)
return;
drawInfo.source = subrect(m_imageSize, m_left.slice, m_top.slice,
sourceSize.width(), sourceSize.height());
drawInfo.destination =
subrect(m_borderImageArea, m_left.width, m_top.width,
destinationSize.width(), destinationSize.height());
FloatSize middleScaleFactor(1, 1);
if (m_top.isDrawable())
middleScaleFactor.setWidth(m_top.scale());
else if (m_bottom.isDrawable())
middleScaleFactor.setWidth(m_bottom.scale());
if (m_left.isDrawable())
middleScaleFactor.setHeight(m_left.scale());
else if (m_right.isDrawable())
middleScaleFactor.setHeight(m_right.scale());
if (!sourceSize.isEmpty()) {
// For "stretch" rules, just override the scale factor and replace. We only
// have to do this for the center tile, since sides don't even use the scale
// factor unless they have a rule other than "stretch". The middle however
// can have "stretch" specified in one axis but not the other, so we have to
// correct the scale here.
if (m_horizontalTileRule == (Image::TileRule)StretchImageRule)
middleScaleFactor.setWidth((float)destinationSize.width() /
sourceSize.width());
if (m_verticalTileRule == (Image::TileRule)StretchImageRule)
middleScaleFactor.setHeight((float)destinationSize.height() /
sourceSize.height());
}
drawInfo.tileScale = middleScaleFactor;
drawInfo.tileRule = {m_horizontalTileRule, m_verticalTileRule};
}
NinePieceImageGrid::NinePieceDrawInfo NinePieceImageGrid::getNinePieceDrawInfo(
NinePiece piece,
float imageScaleFactor) const {
DCHECK_NE(imageScaleFactor, 0);
NinePieceDrawInfo drawInfo;
drawInfo.isCornerPiece = piece == TopLeftPiece || piece == TopRightPiece ||
piece == BottomLeftPiece ||
piece == BottomRightPiece;
if (drawInfo.isCornerPiece)
setDrawInfoCorner(drawInfo, piece);
else if (piece != MiddlePiece)
setDrawInfoEdge(drawInfo, piece);
else
setDrawInfoMiddle(drawInfo);
if (imageScaleFactor != 1) {
// The nine piece grid is computed in unscaled image coordinates but must be
// drawn using scaled image coordinates.
drawInfo.source.scale(imageScaleFactor);
// Compensate for source scaling by scaling down the individual tiles.
drawInfo.tileScale.scale(1 / imageScaleFactor);
}
return drawInfo;
}
} // namespace blink