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