1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ui/wm/core/image_grid.h"
6
7#include <algorithm>
8
9#include "third_party/skia/include/core/SkColor.h"
10#include "third_party/skia/include/core/SkXfermode.h"
11#include "ui/compositor/dip_util.h"
12#include "ui/gfx/canvas.h"
13#include "ui/gfx/image/image.h"
14#include "ui/gfx/image/image_skia_operations.h"
15#include "ui/gfx/rect.h"
16#include "ui/gfx/rect_conversions.h"
17#include "ui/gfx/size.h"
18#include "ui/gfx/size_conversions.h"
19#include "ui/gfx/transform.h"
20
21using std::max;
22using std::min;
23
24namespace wm {
25namespace {
26
27// Sets the scaling for the transform applied to a layer.  The left, top,
28// right and bottom layers are stretched to the height or width of the
29// center image.
30
31void ScaleWidth(gfx::Size center, ui::Layer* layer, gfx::Transform& transform) {
32  float layer_width = layer->bounds().width() * layer->device_scale_factor();
33  float scale = static_cast<float>(center.width()) / layer_width;
34  transform.Scale(scale, 1.0);
35}
36
37void ScaleHeight(gfx::Size center,
38                 ui::Layer* layer,
39                 gfx::Transform& transform) {
40  float layer_height = layer->bounds().height() * layer->device_scale_factor();
41  float scale = static_cast<float>(center.height()) / layer_height;
42  transform.Scale(1.0, scale);
43}
44
45// Returns the dimensions of |image| if non-NULL or gfx::Size(0, 0) otherwise.
46gfx::Size GetImageSize(const gfx::Image* image) {
47  return image ? gfx::Size(image->ToImageSkia()->width(),
48                           image->ToImageSkia()->height())
49               : gfx::Size();
50}
51
52// Returns true if |layer|'s bounds don't fit within |size|.
53bool LayerExceedsSize(const ui::Layer* layer, const gfx::Size& size) {
54  return layer->bounds().width() > size.width() ||
55         layer->bounds().height() > size.height();
56}
57
58}  // namespace
59
60gfx::RectF ImageGrid::TestAPI::GetTransformedLayerBounds(
61    const ui::Layer& layer) {
62  gfx::RectF bounds = layer.bounds();
63  layer.transform().TransformRect(&bounds);
64  return bounds;
65}
66
67ImageGrid::ImageGrid()
68    : layer_(new ui::Layer(ui::LAYER_NOT_DRAWN)),
69      top_image_height_(0),
70      bottom_image_height_(0),
71      left_image_width_(0),
72      right_image_width_(0),
73      base_top_row_height_(0),
74      base_bottom_row_height_(0),
75      base_left_column_width_(0),
76      base_right_column_width_(0) {
77}
78
79ImageGrid::~ImageGrid() {
80}
81
82void ImageGrid::SetImages(const gfx::Image* top_left_image,
83                          const gfx::Image* top_image,
84                          const gfx::Image* top_right_image,
85                          const gfx::Image* left_image,
86                          const gfx::Image* center_image,
87                          const gfx::Image* right_image,
88                          const gfx::Image* bottom_left_image,
89                          const gfx::Image* bottom_image,
90                          const gfx::Image* bottom_right_image) {
91  SetImage(top_left_image, &top_left_layer_, &top_left_painter_, NONE);
92  SetImage(top_image, &top_layer_, &top_painter_, HORIZONTAL);
93  SetImage(top_right_image, &top_right_layer_, &top_right_painter_, NONE);
94  SetImage(left_image, &left_layer_, &left_painter_, VERTICAL);
95  SetImage(center_image, &center_layer_, &center_painter_, NONE);
96  SetImage(right_image, &right_layer_, &right_painter_, VERTICAL);
97  SetImage(bottom_left_image, &bottom_left_layer_, &bottom_left_painter_, NONE);
98  SetImage(bottom_image, &bottom_layer_, &bottom_painter_, HORIZONTAL);
99  SetImage(
100      bottom_right_image, &bottom_right_layer_, &bottom_right_painter_, NONE);
101
102  top_image_height_ = GetImageSize(top_image).height();
103  bottom_image_height_ = GetImageSize(bottom_image).height();
104  left_image_width_ = GetImageSize(left_image).width();
105  right_image_width_ = GetImageSize(right_image).width();
106
107  base_top_row_height_ = max(GetImageSize(top_left_image).height(),
108                             max(GetImageSize(top_image).height(),
109                                 GetImageSize(top_right_image).height()));
110  base_bottom_row_height_ = max(GetImageSize(bottom_left_image).height(),
111                                max(GetImageSize(bottom_image).height(),
112                                    GetImageSize(bottom_right_image).height()));
113  base_left_column_width_ = max(GetImageSize(top_left_image).width(),
114                                max(GetImageSize(left_image).width(),
115                                    GetImageSize(bottom_left_image).width()));
116  base_right_column_width_ = max(GetImageSize(top_right_image).width(),
117                                 max(GetImageSize(right_image).width(),
118                                     GetImageSize(bottom_right_image).width()));
119
120  // Invalidate previous |size_| so calls to SetSize() will recompute it.
121  size_.SetSize(0, 0);
122}
123
124void ImageGrid::SetSize(const gfx::Size& size) {
125  if (size_ == size)
126    return;
127
128  size_ = size;
129
130  gfx::Rect updated_bounds = layer_->bounds();
131  updated_bounds.set_size(size);
132  layer_->SetBounds(updated_bounds);
133
134  // Calculate the available amount of space for corner images on all sides of
135  // the grid.  If the images don't fit, we need to clip them.
136  const int left = min(base_left_column_width_, size_.width() / 2);
137  const int right = min(base_right_column_width_, size_.width() - left);
138  const int top = min(base_top_row_height_, size_.height() / 2);
139  const int bottom = min(base_bottom_row_height_, size_.height() - top);
140
141  // The remaining space goes to the center image.
142  int center_width = std::max(size.width() - left - right, 0);
143  int center_height = std::max(size.height() - top - bottom, 0);
144
145  // At non-integer scale factors, the ratio of dimensions in DIP is not
146  // necessarily the same as the ratio in physical pixels due to rounding.  Set
147  // the transform on each of the layers based on dimensions in pixels.
148  gfx::Size center_size_in_pixels = gfx::ToFlooredSize(gfx::ScaleSize(
149      gfx::Size(center_width, center_height), layer_->device_scale_factor()));
150
151  if (top_layer_.get()) {
152    if (center_width > 0) {
153      gfx::Transform transform;
154      transform.Translate(left, 0);
155      ScaleWidth(center_size_in_pixels, top_layer_.get(), transform);
156      top_layer_->SetTransform(transform);
157    }
158    top_layer_->SetVisible(center_width > 0);
159  }
160  if (bottom_layer_.get()) {
161    if (center_width > 0) {
162      gfx::Transform transform;
163      transform.Translate(
164          left, size.height() - bottom_layer_->bounds().height());
165      ScaleWidth(center_size_in_pixels, bottom_layer_.get(), transform);
166      bottom_layer_->SetTransform(transform);
167    }
168    bottom_layer_->SetVisible(center_width > 0);
169  }
170  if (left_layer_.get()) {
171    if (center_height > 0) {
172      gfx::Transform transform;
173      transform.Translate(0, top);
174      ScaleHeight(center_size_in_pixels, left_layer_.get(), transform);
175      left_layer_->SetTransform(transform);
176    }
177    left_layer_->SetVisible(center_height > 0);
178  }
179  if (right_layer_.get()) {
180    if (center_height > 0) {
181      gfx::Transform transform;
182      transform.Translate(
183          size.width() - right_layer_->bounds().width(), top);
184      ScaleHeight(center_size_in_pixels, right_layer_.get(), transform);
185      right_layer_->SetTransform(transform);
186    }
187    right_layer_->SetVisible(center_height > 0);
188  }
189
190  if (top_left_layer_.get()) {
191    // No transformation needed; it should be at (0, 0) and unscaled.
192    top_left_painter_->SetClipRect(
193        LayerExceedsSize(top_left_layer_.get(), gfx::Size(left, top)) ?
194            gfx::Rect(gfx::Rect(0, 0, left, top)) :
195            gfx::Rect(),
196        top_left_layer_.get());
197  }
198  if (top_right_layer_.get()) {
199    gfx::Transform transform;
200    transform.Translate(size.width() - top_right_layer_->bounds().width(), 0.0);
201    top_right_layer_->SetTransform(transform);
202    top_right_painter_->SetClipRect(
203        LayerExceedsSize(top_right_layer_.get(), gfx::Size(right, top)) ?
204            gfx::Rect(top_right_layer_->bounds().width() - right, 0,
205                      right, top) :
206            gfx::Rect(),
207        top_right_layer_.get());
208  }
209  if (bottom_left_layer_.get()) {
210    gfx::Transform transform;
211    transform.Translate(
212        0.0, size.height() - bottom_left_layer_->bounds().height());
213    bottom_left_layer_->SetTransform(transform);
214    bottom_left_painter_->SetClipRect(
215        LayerExceedsSize(bottom_left_layer_.get(), gfx::Size(left, bottom)) ?
216            gfx::Rect(0, bottom_left_layer_->bounds().height() - bottom,
217                      left, bottom) :
218            gfx::Rect(),
219        bottom_left_layer_.get());
220  }
221  if (bottom_right_layer_.get()) {
222    gfx::Transform transform;
223    transform.Translate(
224        size.width() - bottom_right_layer_->bounds().width(),
225        size.height() - bottom_right_layer_->bounds().height());
226    bottom_right_layer_->SetTransform(transform);
227    bottom_right_painter_->SetClipRect(
228        LayerExceedsSize(bottom_right_layer_.get(), gfx::Size(right, bottom)) ?
229            gfx::Rect(bottom_right_layer_->bounds().width() - right,
230                      bottom_right_layer_->bounds().height() - bottom,
231                      right, bottom) :
232            gfx::Rect(),
233        bottom_right_layer_.get());
234  }
235
236  if (center_layer_.get()) {
237    if (center_width > 0 && center_height > 0) {
238      gfx::Transform transform;
239      transform.Translate(left, top);
240      transform.Scale(center_width / center_layer_->bounds().width(),
241                      center_height / center_layer_->bounds().height());
242      center_layer_->SetTransform(transform);
243    }
244    center_layer_->SetVisible(center_width > 0 && center_height > 0);
245  }
246}
247
248void ImageGrid::SetContentBounds(const gfx::Rect& content_bounds) {
249
250  SetSize(gfx::Size(
251      content_bounds.width() + left_image_width_ + right_image_width_,
252      content_bounds.height() + top_image_height_ +
253      bottom_image_height_));
254  layer_->SetBounds(
255      gfx::Rect(content_bounds.x() - left_image_width_,
256                content_bounds.y() - top_image_height_,
257                layer_->bounds().width(),
258                layer_->bounds().height()));
259}
260
261void ImageGrid::ImagePainter::SetClipRect(const gfx::Rect& clip_rect,
262                                          ui::Layer* layer) {
263  if (clip_rect != clip_rect_) {
264    clip_rect_ = clip_rect;
265    layer->SchedulePaint(layer->bounds());
266  }
267}
268
269void ImageGrid::ImagePainter::OnPaintLayer(gfx::Canvas* canvas) {
270  if (!clip_rect_.IsEmpty())
271    canvas->ClipRect(clip_rect_);
272  canvas->DrawImageInt(image_, 0, 0);
273}
274
275void ImageGrid::ImagePainter::OnDelegatedFrameDamage(
276    const gfx::Rect& damage_rect_in_dip) {
277}
278
279void ImageGrid::ImagePainter::OnDeviceScaleFactorChanged(
280    float device_scale_factor) {
281  // Redrawing will take care of scale factor change.
282}
283
284base::Closure ImageGrid::ImagePainter::PrepareForLayerBoundsChange() {
285  return base::Closure();
286}
287
288void ImageGrid::SetImage(const gfx::Image* image,
289                         scoped_ptr<ui::Layer>* layer_ptr,
290                         scoped_ptr<ImagePainter>* painter_ptr,
291                         ImageType type) {
292  // Minimum width (for HORIZONTAL) or height (for VERTICAL) of the
293  // |image| so that layers are scaled property if the device scale
294  // factor is non integral.
295  const int kMinimumSize = 20;
296
297  // Clean out old layers and painters.
298  if (layer_ptr->get())
299    layer_->Remove(layer_ptr->get());
300  layer_ptr->reset();
301  painter_ptr->reset();
302
303  // If we're not using an image, we're done.
304  if (!image)
305    return;
306
307  gfx::ImageSkia image_skia = image->AsImageSkia();
308  switch (type) {
309    case HORIZONTAL:
310      if (image_skia.width() < kMinimumSize) {
311        image_skia = gfx::ImageSkiaOperations::CreateResizedImage(
312            image_skia,
313            skia::ImageOperations::RESIZE_GOOD,
314            gfx::Size(kMinimumSize, image_skia.height()));
315      }
316      break;
317    case VERTICAL:
318      if (image_skia.height() < kMinimumSize) {
319        image_skia = gfx::ImageSkiaOperations::CreateResizedImage(
320            image_skia,
321            skia::ImageOperations::RESIZE_GOOD,
322            gfx::Size(image_skia.width(), kMinimumSize));
323      }
324      break;
325    case NONE:
326      break;
327  }
328
329  // Set up the new layer and painter.
330  layer_ptr->reset(new ui::Layer(ui::LAYER_TEXTURED));
331
332  const gfx::Size size = image_skia.size();
333  layer_ptr->get()->SetBounds(gfx::Rect(0, 0, size.width(), size.height()));
334
335  painter_ptr->reset(new ImagePainter(image_skia));
336  layer_ptr->get()->set_delegate(painter_ptr->get());
337  layer_ptr->get()->SetFillsBoundsOpaquely(false);
338  layer_ptr->get()->SetVisible(true);
339  layer_->Add(layer_ptr->get());
340}
341
342}  // namespace wm
343