summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/paint/nine_piece_image_painter.cc
blob: b903af4b71d82c9d8ba648757d0a3781334f8660 (plain)
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
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// 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()));
  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