blob: 17c57a48a87599e981abdb88c8d8bac9086df221 [file] [log] [blame]
Mattias Nisslerb1fdeb5a2015-07-09 12:10:391// Copyright 2015 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/gfx/paint_vector_icon.h"
6
estade2ee31b32015-07-29 16:49:547#include <map>
jsbell7faa0862015-11-20 01:26:568#include <tuple>
estade2ee31b32015-07-29 16:49:549
10#include "base/lazy_instance.h"
estade6c874602015-08-04 22:45:2411#include "base/strings/string_number_conversions.h"
12#include "base/strings/string_split.h"
estade7ca51e22015-08-31 19:03:3313#include "third_party/skia/include/core/SkPaint.h"
bungemanac161892015-08-03 19:40:3114#include "third_party/skia/include/core/SkPath.h"
estade7ca51e22015-08-31 19:03:3315#include "third_party/skia/include/core/SkXfermode.h"
Mattias Nisslerb1fdeb5a2015-07-09 12:10:3916#include "ui/gfx/canvas.h"
estadea204e0c2015-07-22 19:54:3117#include "ui/gfx/image/canvas_image_source.h"
estade0a679f82015-07-16 19:31:5618#include "ui/gfx/vector_icon_types.h"
estade73e2dc12015-08-31 22:43:3119#include "ui/gfx/vector_icons.h"
Mattias Nisslerb1fdeb5a2015-07-09 12:10:3920
21namespace gfx {
22
estadea204e0c2015-07-22 19:54:3123namespace {
24
estade6c874602015-08-04 22:45:2425// Translates a string such as "MOVE_TO" into a command such as MOVE_TO.
26CommandType CommandFromString(const std::string& source) {
27#define RETURN_IF_IS(command) \
28 if (source == #command) \
29 return command;
estadea204e0c2015-07-22 19:54:3130
estadedbdbdfe2015-08-05 23:05:0731 RETURN_IF_IS(NEW_PATH);
32 RETURN_IF_IS(PATH_COLOR_ARGB);
estade7ca51e22015-08-31 19:03:3333 RETURN_IF_IS(PATH_MODE_CLEAR);
Evan Stadecedd7da72015-08-06 22:58:4334 RETURN_IF_IS(STROKE);
estadeab97e1d2015-09-21 22:42:2235 RETURN_IF_IS(CAP_SQUARE);
estade6c874602015-08-04 22:45:2436 RETURN_IF_IS(MOVE_TO);
37 RETURN_IF_IS(R_MOVE_TO);
estadedbdbdfe2015-08-05 23:05:0738 RETURN_IF_IS(LINE_TO);
estade6c874602015-08-04 22:45:2439 RETURN_IF_IS(R_LINE_TO);
40 RETURN_IF_IS(H_LINE_TO);
41 RETURN_IF_IS(R_H_LINE_TO);
42 RETURN_IF_IS(V_LINE_TO);
43 RETURN_IF_IS(R_V_LINE_TO);
44 RETURN_IF_IS(CUBIC_TO);
45 RETURN_IF_IS(R_CUBIC_TO);
46 RETURN_IF_IS(CIRCLE);
estade490f73b2015-09-25 01:40:4947 RETURN_IF_IS(ROUND_RECT);
estade6c874602015-08-04 22:45:2448 RETURN_IF_IS(CLOSE);
estadedbdbdfe2015-08-05 23:05:0749 RETURN_IF_IS(CANVAS_DIMENSIONS);
Evan Stadecedd7da72015-08-06 22:58:4350 RETURN_IF_IS(CLIP);
estade20db97e72015-08-13 17:46:5851 RETURN_IF_IS(DISABLE_AA);
estade6c874602015-08-04 22:45:2452 RETURN_IF_IS(END);
53#undef RETURN_IF_IS
estadea204e0c2015-07-22 19:54:3154
estade6c874602015-08-04 22:45:2455 NOTREACHED();
56 return CLOSE;
57}
58
59std::vector<PathElement> PathFromSource(const std::string& source) {
60 std::vector<PathElement> path;
61 std::vector<std::string> pieces = base::SplitString(
62 source, "\n ,f", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
63 for (const auto& piece : pieces) {
64 double value;
65 if (base::StringToDouble(piece, &value))
66 path.push_back(PathElement(SkDoubleToScalar(value)));
67 else
68 path.push_back(PathElement(CommandFromString(piece)));
estadea204e0c2015-07-22 19:54:3169 }
estade6c874602015-08-04 22:45:2470 return path;
71}
estadea204e0c2015-07-22 19:54:3172
estade6c874602015-08-04 22:45:2473void PaintPath(Canvas* canvas,
74 const PathElement* path_elements,
75 size_t dip_size,
76 SkColor color) {
estade7ca51e22015-08-31 19:03:3377 canvas->Save();
estade6c874602015-08-04 22:45:2478 SkPath path;
79 path.setFillType(SkPath::kEvenOdd_FillType);
estade804d2472015-08-03 18:54:3480
estade804d2472015-08-03 18:54:3481 size_t canvas_size = kReferenceSizeDip;
estade20db97e72015-08-13 17:46:5882 std::vector<SkPath> paths;
estadedbdbdfe2015-08-05 23:05:0783 std::vector<SkPaint> paints;
Evan Stadecedd7da72015-08-06 22:58:4384 SkRect clip_rect = SkRect::MakeEmpty();
85
estadeeaac88b2015-07-30 17:29:3986 for (size_t i = 0; path_elements[i].type != END; i++) {
estade20db97e72015-08-13 17:46:5887 if (paths.empty() || path_elements[i].type == NEW_PATH) {
88 paths.push_back(SkPath());
89 paths.back().setFillType(SkPath::kEvenOdd_FillType);
90
91 paints.push_back(SkPaint());
92 paints.back().setColor(color);
93 paints.back().setAntiAlias(true);
94 paints.back().setStrokeCap(SkPaint::kRound_Cap);
95 }
96
estade804d2472015-08-03 18:54:3497 SkPath& path = paths.back();
Evan Stadecedd7da72015-08-06 22:58:4398 SkPaint& paint = paints.back();
estadeeaac88b2015-07-30 17:29:3999 switch (path_elements[i].type) {
estade20db97e72015-08-13 17:46:58100 // Handled above.
101 case NEW_PATH:
102 continue;
estadedbdbdfe2015-08-05 23:05:07103
104 case PATH_COLOR_ARGB: {
105 int a = SkScalarFloorToInt(path_elements[++i].arg);
106 int r = SkScalarFloorToInt(path_elements[++i].arg);
107 int g = SkScalarFloorToInt(path_elements[++i].arg);
108 int b = SkScalarFloorToInt(path_elements[++i].arg);
Evan Stadecedd7da72015-08-06 22:58:43109 paint.setColor(SkColorSetARGB(a, r, g, b));
110 break;
111 }
112
estade7ca51e22015-08-31 19:03:33113 case PATH_MODE_CLEAR: {
114 paint.setXfermodeMode(SkXfermode::kClear_Mode);
115 break;
116 };
117
Evan Stadecedd7da72015-08-06 22:58:43118 case STROKE: {
119 paint.setStyle(SkPaint::kStroke_Style);
120 SkScalar width = path_elements[++i].arg;
121 paint.setStrokeWidth(width);
estade804d2472015-08-03 18:54:34122 break;
123 }
124
estadeab97e1d2015-09-21 22:42:22125 case CAP_SQUARE: {
126 paint.setStrokeCap(SkPaint::kSquare_Cap);
127 break;
128 }
129
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39130 case MOVE_TO: {
estadeeaac88b2015-07-30 17:29:39131 SkScalar x = path_elements[++i].arg;
132 SkScalar y = path_elements[++i].arg;
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39133 path.moveTo(x, y);
134 break;
135 }
136
137 case R_MOVE_TO: {
estadeeaac88b2015-07-30 17:29:39138 SkScalar x = path_elements[++i].arg;
139 SkScalar y = path_elements[++i].arg;
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39140 path.rMoveTo(x, y);
141 break;
142 }
143
144 case LINE_TO: {
estadeeaac88b2015-07-30 17:29:39145 SkScalar x = path_elements[++i].arg;
146 SkScalar y = path_elements[++i].arg;
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39147 path.lineTo(x, y);
148 break;
149 }
150
151 case R_LINE_TO: {
estadeeaac88b2015-07-30 17:29:39152 SkScalar x = path_elements[++i].arg;
153 SkScalar y = path_elements[++i].arg;
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39154 path.rLineTo(x, y);
155 break;
156 }
157
158 case H_LINE_TO: {
159 SkPoint last_point;
160 path.getLastPt(&last_point);
estadeeaac88b2015-07-30 17:29:39161 SkScalar x = path_elements[++i].arg;
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39162 path.lineTo(x, last_point.fY);
163 break;
164 }
165
166 case R_H_LINE_TO: {
estadeeaac88b2015-07-30 17:29:39167 SkScalar x = path_elements[++i].arg;
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39168 path.rLineTo(x, 0);
169 break;
170 }
171
172 case V_LINE_TO: {
173 SkPoint last_point;
174 path.getLastPt(&last_point);
estadeeaac88b2015-07-30 17:29:39175 SkScalar y = path_elements[++i].arg;
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39176 path.lineTo(last_point.fX, y);
177 break;
178 }
179
180 case R_V_LINE_TO: {
estadeeaac88b2015-07-30 17:29:39181 SkScalar y = path_elements[++i].arg;
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39182 path.rLineTo(0, y);
183 break;
184 }
185
estadee1416092015-07-29 18:24:12186 case CUBIC_TO: {
estadeeaac88b2015-07-30 17:29:39187 SkScalar x1 = path_elements[++i].arg;
188 SkScalar y1 = path_elements[++i].arg;
189 SkScalar x2 = path_elements[++i].arg;
190 SkScalar y2 = path_elements[++i].arg;
191 SkScalar x3 = path_elements[++i].arg;
192 SkScalar y3 = path_elements[++i].arg;
estadee1416092015-07-29 18:24:12193 path.cubicTo(x1, y1, x2, y2, x3, y3);
194 break;
195 }
196
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39197 case R_CUBIC_TO: {
estadeeaac88b2015-07-30 17:29:39198 SkScalar x1 = path_elements[++i].arg;
199 SkScalar y1 = path_elements[++i].arg;
200 SkScalar x2 = path_elements[++i].arg;
201 SkScalar y2 = path_elements[++i].arg;
202 SkScalar x3 = path_elements[++i].arg;
203 SkScalar y3 = path_elements[++i].arg;
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39204 path.rCubicTo(x1, y1, x2, y2, x3, y3);
205 break;
206 }
207
estadeeaac88b2015-07-30 17:29:39208 case CIRCLE: {
209 SkScalar x = path_elements[++i].arg;
210 SkScalar y = path_elements[++i].arg;
211 SkScalar r = path_elements[++i].arg;
212 path.addCircle(x, y, r);
213 break;
214 }
215
estade490f73b2015-09-25 01:40:49216 case ROUND_RECT: {
217 SkScalar x = path_elements[++i].arg;
218 SkScalar y = path_elements[++i].arg;
219 SkScalar w = path_elements[++i].arg;
220 SkScalar h = path_elements[++i].arg;
221 SkScalar radius = path_elements[++i].arg;
222 path.addRoundRect(SkRect::MakeXYWH(x, y, w, h), radius, radius);
223 break;
224 }
225
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39226 case CLOSE: {
227 path.close();
228 break;
229 }
230
estade804d2472015-08-03 18:54:34231 case CANVAS_DIMENSIONS: {
232 SkScalar width = path_elements[++i].arg;
233 canvas_size = SkScalarTruncToInt(width);
234 break;
235 }
236
Evan Stadecedd7da72015-08-06 22:58:43237 case CLIP: {
238 SkScalar x = path_elements[++i].arg;
239 SkScalar y = path_elements[++i].arg;
240 SkScalar w = path_elements[++i].arg;
241 SkScalar h = path_elements[++i].arg;
242 clip_rect = SkRect::MakeXYWH(x, y, w, h);
243 break;
244 }
245
estade20db97e72015-08-13 17:46:58246 case DISABLE_AA: {
247 paint.setAntiAlias(false);
248 break;
249 }
250
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39251 case END:
252 NOTREACHED();
253 break;
254 }
255 }
256
estade804d2472015-08-03 18:54:34257 if (dip_size != canvas_size) {
258 SkScalar scale = SkIntToScalar(dip_size) / SkIntToScalar(canvas_size);
259 canvas->sk_canvas()->scale(scale, scale);
260 }
261
Evan Stadecedd7da72015-08-06 22:58:43262 if (!clip_rect.isEmpty())
263 canvas->sk_canvas()->clipRect(clip_rect);
264
estadedbdbdfe2015-08-05 23:05:07265 DCHECK_EQ(paints.size(), paths.size());
estade20db97e72015-08-13 17:46:58266 for (size_t i = 0; i < paths.size(); ++i)
estadedbdbdfe2015-08-05 23:05:07267 canvas->DrawPath(paths[i], paints[i]);
estade7ca51e22015-08-31 19:03:33268 canvas->Restore();
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39269}
270
estade6c874602015-08-04 22:45:24271class VectorIconSource : public CanvasImageSource {
272 public:
estade7ca51e22015-08-31 19:03:33273 VectorIconSource(VectorIconId id,
274 size_t dip_size,
275 SkColor color,
276 VectorIconId badge_id)
estade6c874602015-08-04 22:45:24277 : CanvasImageSource(
278 gfx::Size(static_cast<int>(dip_size), static_cast<int>(dip_size)),
279 false),
280 id_(id),
estade7ca51e22015-08-31 19:03:33281 color_(color),
282 badge_id_(badge_id) {}
estade6c874602015-08-04 22:45:24283
284 VectorIconSource(const std::string& definition,
285 size_t dip_size,
286 SkColor color)
287 : CanvasImageSource(
288 gfx::Size(static_cast<int>(dip_size), static_cast<int>(dip_size)),
289 false),
290 id_(VectorIconId::VECTOR_ICON_NONE),
291 path_(PathFromSource(definition)),
estade7ca51e22015-08-31 19:03:33292 color_(color),
293 badge_id_(VectorIconId::VECTOR_ICON_NONE) {}
estade6c874602015-08-04 22:45:24294
295 ~VectorIconSource() override {}
296
297 // CanvasImageSource:
298 void Draw(gfx::Canvas* canvas) override {
estade7ca51e22015-08-31 19:03:33299 if (path_.empty()) {
estade6c874602015-08-04 22:45:24300 PaintVectorIcon(canvas, id_, size_.width(), color_);
estade7ca51e22015-08-31 19:03:33301 if (badge_id_ != VectorIconId::VECTOR_ICON_NONE)
302 PaintVectorIcon(canvas, badge_id_, size_.width(), color_);
303 } else {
estade6c874602015-08-04 22:45:24304 PaintPath(canvas, path_.data(), size_.width(), color_);
estade7ca51e22015-08-31 19:03:33305 }
estade6c874602015-08-04 22:45:24306 }
307
308 private:
309 const VectorIconId id_;
310 const std::vector<PathElement> path_;
311 const SkColor color_;
estade7ca51e22015-08-31 19:03:33312 const VectorIconId badge_id_;
estade6c874602015-08-04 22:45:24313
314 DISALLOW_COPY_AND_ASSIGN(VectorIconSource);
315};
316
317// This class caches vector icons (as ImageSkia) so they don't have to be drawn
318// more than once. This also guarantees the backing data for the images returned
319// by CreateVectorIcon will persist in memory until program termination.
320class VectorIconCache {
321 public:
322 VectorIconCache() {}
323 ~VectorIconCache() {}
324
estade7ca51e22015-08-31 19:03:33325 ImageSkia GetOrCreateIcon(VectorIconId id,
326 size_t dip_size,
327 SkColor color,
328 VectorIconId badge_id) {
329 IconDescription description(id, dip_size, color, badge_id);
estade6c874602015-08-04 22:45:24330 auto iter = images_.find(description);
331 if (iter != images_.end())
332 return iter->second;
333
334 ImageSkia icon(
estade7ca51e22015-08-31 19:03:33335 new VectorIconSource(id, dip_size, color, badge_id),
estade6c874602015-08-04 22:45:24336 gfx::Size(static_cast<int>(dip_size), static_cast<int>(dip_size)));
337 images_.insert(std::make_pair(description, icon));
338 return icon;
339 }
340
341 private:
342 struct IconDescription {
estade7ca51e22015-08-31 19:03:33343 IconDescription(VectorIconId id,
344 size_t dip_size,
345 SkColor color,
346 VectorIconId badge_id)
347 : id(id), dip_size(dip_size), color(color), badge_id(badge_id) {}
estade6c874602015-08-04 22:45:24348
349 bool operator<(const IconDescription& other) const {
jsbell7faa0862015-11-20 01:26:56350 return std::tie(id, dip_size, color, badge_id) <
351 std::tie(other.id, other.dip_size, other.color, other.badge_id);
estade6c874602015-08-04 22:45:24352 }
353
354 VectorIconId id;
355 size_t dip_size;
356 SkColor color;
estade7ca51e22015-08-31 19:03:33357 VectorIconId badge_id;
estade6c874602015-08-04 22:45:24358 };
359
360 std::map<IconDescription, ImageSkia> images_;
361
362 DISALLOW_COPY_AND_ASSIGN(VectorIconCache);
363};
364
365static base::LazyInstance<VectorIconCache> g_icon_cache =
366 LAZY_INSTANCE_INITIALIZER;
367
368} // namespace
369
370void PaintVectorIcon(Canvas* canvas,
371 VectorIconId id,
372 size_t dip_size,
373 SkColor color) {
374 DCHECK(VectorIconId::VECTOR_ICON_NONE != id);
Evan Stadecedd7da72015-08-06 22:58:43375 const PathElement* path = canvas->image_scale() == 1.f
376 ? GetPathForVectorIconAt1xScale(id)
377 : GetPathForVectorIcon(id);
378 PaintPath(canvas, path, dip_size, color);
estade6c874602015-08-04 22:45:24379}
380
estadea204e0c2015-07-22 19:54:31381ImageSkia CreateVectorIcon(VectorIconId id, size_t dip_size, SkColor color) {
estade7ca51e22015-08-31 19:03:33382 return CreateVectorIconWithBadge(id, dip_size, color,
383 VectorIconId::VECTOR_ICON_NONE);
384}
385
386ImageSkia CreateVectorIconWithBadge(VectorIconId id,
387 size_t dip_size,
388 SkColor color,
389 VectorIconId badge_id) {
390 return g_icon_cache.Get().GetOrCreateIcon(id, dip_size, color, badge_id);
estadea204e0c2015-07-22 19:54:31391}
392
estade6c874602015-08-04 22:45:24393ImageSkia CreateVectorIconFromSource(const std::string& source,
394 size_t dip_size,
395 SkColor color) {
396 return ImageSkia(
397 new VectorIconSource(source, dip_size, color),
398 gfx::Size(static_cast<int>(dip_size), static_cast<int>(dip_size)));
399}
400
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39401} // namespace gfx