blob: ca91cbaff744fafbcf5899309cee4c4ed983485e [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 "third_party/blink/renderer/core/paint/nine_piece_image_painter.h"
#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
#include "third_party/blink/renderer/core/paint/nine_piece_image_grid.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/core/style/nine_piece_image.h"
#include "third_party/blink/renderer/platform/geometry/int_size.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
#include "third_party/blink/renderer/platform/graphics/scoped_interpolation_quality.h"
namespace blink {
namespace {
base::Optional<float> CalculateSpaceNeeded(const float destination,
const float source) {
DCHECK_GT(source, 0);
DCHECK_GT(destination, 0);
float repeat_tiles_count = floorf(destination / source);
if (!repeat_tiles_count)
return base::nullopt;
float space = destination;
space -= source * repeat_tiles_count;
space /= repeat_tiles_count + 1.0;
return space;
}
struct TileParameters {
float scale_factor;
float phase;
float spacing;
};
base::Optional<TileParameters> ComputeTileParameters(
ENinePieceImageRule tile_rule,
float dst_pos,
float dst_extent,
float src_pos,
float src_extent,
float in_scale_factor) {
switch (tile_rule) {
case kRoundImageRule: {
float repetitions =
std::max(1.0f, roundf(dst_extent / (src_extent * in_scale_factor)));
float scale_factor = dst_extent / (src_extent * repetitions);
return TileParameters{scale_factor, src_pos * scale_factor, 0};
}
case kRepeatImageRule: {
float scaled_tile_extent = src_extent * in_scale_factor;
// We want to construct the phase such that the pattern is centered (when
// stretch is not set for a particular rule).
float phase = src_pos * in_scale_factor;
phase -= (dst_extent - scaled_tile_extent) / 2;
return TileParameters{in_scale_factor, phase, 0};
}
case kSpaceImageRule: {
base::Optional<float> spacing =
CalculateSpaceNeeded(dst_extent, src_extent);
if (!spacing)
return base::nullopt;
return TileParameters{1, src_pos - *spacing, *spacing};
}
case kStretchImageRule:
return TileParameters{in_scale_factor, src_pos * in_scale_factor, 0};
default:
NOTREACHED();
}
return base::nullopt;
}
void PaintPieces(GraphicsContext& context,
const PhysicalRect& border_image_rect,
const ComputedStyle& style,
const NinePieceImage& nine_piece_image,
Image* image,
IntSize image_size,
bool include_logical_left_edge,
bool include_logical_right_edge) {
IntRectOutsets border_widths(style.BorderTopWidth(), style.BorderRightWidth(),
style.BorderBottomWidth(),
style.BorderLeftWidth());
NinePieceImageGrid grid(
nine_piece_image, image_size, PixelSnappedIntRect(border_image_rect),
border_widths, include_logical_left_edge, include_logical_right_edge);
for (NinePiece piece = kMinPiece; piece < kMaxPiece; ++piece) {
NinePieceImageGrid::NinePieceDrawInfo draw_info = grid.GetNinePieceDrawInfo(
piece, nine_piece_image.GetImage()->ImageScaleFactor());
if (draw_info.is_drawable) {
if (draw_info.is_corner_piece) {
// Since there is no way for the developer to specify decode behavior,
// use kSync by default.
context.DrawImage(image, Image::kSyncDecode, draw_info.destination,
&draw_info.source, style.HasFilterInducingProperty());
} else if (draw_info.tile_rule.horizontal == kStretchImageRule &&
draw_info.tile_rule.vertical == kStretchImageRule) {
// Just do a scale.
// Since there is no way for the developer to specify decode behavior,
// use kSync by default.
context.DrawImage(image, Image::kSyncDecode, draw_info.destination,
&draw_info.source, style.HasFilterInducingProperty());
} else {
// TODO(cavalcantii): see crbug.com/662513.
base::Optional<TileParameters> h_tile = ComputeTileParameters(
draw_info.tile_rule.horizontal, draw_info.destination.X(),
draw_info.destination.Width(), draw_info.source.X(),
draw_info.source.Width(), draw_info.tile_scale.Width());
base::Optional<TileParameters> v_tile = ComputeTileParameters(
draw_info.tile_rule.vertical, draw_info.destination.Y(),
draw_info.destination.Height(), draw_info.source.Y(),
draw_info.source.Height(), draw_info.tile_scale.Height());
if (!h_tile || !v_tile)
continue;
FloatSize tile_scale_factor(h_tile->scale_factor, v_tile->scale_factor);
FloatPoint tile_phase(draw_info.destination.X() - h_tile->phase,
draw_info.destination.Y() - v_tile->phase);
FloatSize tile_spacing(h_tile->spacing, v_tile->spacing);
// TODO(cavalcantii): see crbug.com/662507.
base::Optional<ScopedInterpolationQuality> interpolation_quality_scope;
if (draw_info.tile_rule.horizontal == kRoundImageRule ||
draw_info.tile_rule.vertical == kRoundImageRule)
interpolation_quality_scope.emplace(context, kInterpolationLow);
context.DrawImageTiled(image, draw_info.destination, draw_info.source,
tile_scale_factor, tile_phase, tile_spacing);
}
}
}
}
} // anonymous namespace
bool NinePieceImagePainter::Paint(GraphicsContext& graphics_context,
const ImageResourceObserver& observer,
const Document& document,
Node* node,
const PhysicalRect& rect,
const ComputedStyle& style,
const NinePieceImage& nine_piece_image,
bool include_logical_left_edge,
bool include_logical_right_edge) {
StyleImage* style_image = nine_piece_image.GetImage();
if (!style_image)
return false;
if (!style_image->IsLoaded())
return true; // Never paint a nine-piece image incrementally, but don't
// paint the fallback borders either.
if (!style_image->CanRender())
return false;
// FIXME: border-image is broken with full page zooming when tiling has to
// happen, since the tiling function doesn't have any understanding of the
// zoom that is in effect on the tile.
PhysicalRect rect_with_outsets = rect;
rect_with_outsets.Expand(style.ImageOutsets(nine_piece_image));
PhysicalRect border_image_rect = rect_with_outsets;
// NinePieceImage returns the image slices without effective zoom applied and
// thus we compute the nine piece grid on top of the image in unzoomed
// coordinates.
//
// FIXME: The default object size passed to imageSize() should be scaled by
// the zoom factor passed in. In this case it means that borderImageRect
// should be passed in compensated by effective zoom, since the scale factor
// is one. For generated images, the actual image data (gradient stops, etc.)
// are scaled to effective zoom instead so we must take care not to cause
// scale of them again.
IntSize image_size = RoundedIntSize(
style_image->ImageSize(document, 1, border_image_rect.size.ToLayoutSize(),
kRespectImageOrientation));
scoped_refptr<Image> image =
style_image->GetImage(observer, document, style, FloatSize(image_size));
if (!image)
return true;
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "PaintImage",
"data",
inspector_paint_image_event::Data(node, *style_image,
FloatRect(image->Rect()),
FloatRect(border_image_rect)));
ScopedInterpolationQuality interpolation_quality_scope(
graphics_context, style.GetInterpolationQuality());
PaintPieces(graphics_context, border_image_rect, style, nine_piece_image,
image.get(), image_size, include_logical_left_edge,
include_logical_right_edge);
return true;
}
} // namespace blink