blob: c6a2620d71cffbd05579324234772f50aeae9d68 [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
estadeb054c7c2015-12-22 00:25:0310#include "base/i18n/rtl.h"
estade2ee31b32015-07-29 16:49:5411#include "base/lazy_instance.h"
avic89eb8d42015-12-23 08:08:1812#include "base/macros.h"
estade6c874602015-08-04 22:45:2413#include "base/strings/string_number_conversions.h"
14#include "base/strings/string_split.h"
estade7ca51e22015-08-31 19:03:3315#include "third_party/skia/include/core/SkPaint.h"
bungemanac161892015-08-03 19:40:3116#include "third_party/skia/include/core/SkPath.h"
estade7ca51e22015-08-31 19:03:3317#include "third_party/skia/include/core/SkXfermode.h"
Mattias Nisslerb1fdeb5a2015-07-09 12:10:3918#include "ui/gfx/canvas.h"
estadea204e0c2015-07-22 19:54:3119#include "ui/gfx/image/canvas_image_source.h"
estade0a679f82015-07-16 19:31:5620#include "ui/gfx/vector_icon_types.h"
estade73e2dc12015-08-31 22:43:3121#include "ui/gfx/vector_icons.h"
Mattias Nisslerb1fdeb5a2015-07-09 12:10:3922
23namespace gfx {
24
estadea204e0c2015-07-22 19:54:3125namespace {
26
estade6c874602015-08-04 22:45:2427// Translates a string such as "MOVE_TO" into a command such as MOVE_TO.
28CommandType CommandFromString(const std::string& source) {
29#define RETURN_IF_IS(command) \
30 if (source == #command) \
31 return command;
estadea204e0c2015-07-22 19:54:3132
estadedbdbdfe2015-08-05 23:05:0733 RETURN_IF_IS(NEW_PATH);
34 RETURN_IF_IS(PATH_COLOR_ARGB);
estade7ca51e22015-08-31 19:03:3335 RETURN_IF_IS(PATH_MODE_CLEAR);
Evan Stadecedd7da72015-08-06 22:58:4336 RETURN_IF_IS(STROKE);
estadeab97e1d2015-09-21 22:42:2237 RETURN_IF_IS(CAP_SQUARE);
estade6c874602015-08-04 22:45:2438 RETURN_IF_IS(MOVE_TO);
39 RETURN_IF_IS(R_MOVE_TO);
tdandersonaa185fbd2016-06-09 09:18:1540 RETURN_IF_IS(ARC_TO);
41 RETURN_IF_IS(R_ARC_TO);
estadedbdbdfe2015-08-05 23:05:0742 RETURN_IF_IS(LINE_TO);
estade6c874602015-08-04 22:45:2443 RETURN_IF_IS(R_LINE_TO);
44 RETURN_IF_IS(H_LINE_TO);
45 RETURN_IF_IS(R_H_LINE_TO);
46 RETURN_IF_IS(V_LINE_TO);
47 RETURN_IF_IS(R_V_LINE_TO);
48 RETURN_IF_IS(CUBIC_TO);
49 RETURN_IF_IS(R_CUBIC_TO);
50 RETURN_IF_IS(CIRCLE);
estade490f73b2015-09-25 01:40:4951 RETURN_IF_IS(ROUND_RECT);
estade6c874602015-08-04 22:45:2452 RETURN_IF_IS(CLOSE);
estadedbdbdfe2015-08-05 23:05:0753 RETURN_IF_IS(CANVAS_DIMENSIONS);
Evan Stadecedd7da72015-08-06 22:58:4354 RETURN_IF_IS(CLIP);
estade20db97e72015-08-13 17:46:5855 RETURN_IF_IS(DISABLE_AA);
estadeb054c7c2015-12-22 00:25:0356 RETURN_IF_IS(FLIPS_IN_RTL);
estade6c874602015-08-04 22:45:2457 RETURN_IF_IS(END);
58#undef RETURN_IF_IS
estadea204e0c2015-07-22 19:54:3159
estade6c874602015-08-04 22:45:2460 NOTREACHED();
61 return CLOSE;
62}
63
64std::vector<PathElement> PathFromSource(const std::string& source) {
65 std::vector<PathElement> path;
66 std::vector<std::string> pieces = base::SplitString(
67 source, "\n ,f", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
68 for (const auto& piece : pieces) {
69 double value;
70 if (base::StringToDouble(piece, &value))
71 path.push_back(PathElement(SkDoubleToScalar(value)));
72 else
73 path.push_back(PathElement(CommandFromString(piece)));
estadea204e0c2015-07-22 19:54:3174 }
estade6c874602015-08-04 22:45:2475 return path;
76}
estadea204e0c2015-07-22 19:54:3177
estade6c874602015-08-04 22:45:2478void PaintPath(Canvas* canvas,
79 const PathElement* path_elements,
80 size_t dip_size,
81 SkColor color) {
estade7ca51e22015-08-31 19:03:3382 canvas->Save();
estade6c874602015-08-04 22:45:2483 SkPath path;
84 path.setFillType(SkPath::kEvenOdd_FillType);
estade804d2472015-08-03 18:54:3485
estade804d2472015-08-03 18:54:3486 size_t canvas_size = kReferenceSizeDip;
estade20db97e72015-08-13 17:46:5887 std::vector<SkPath> paths;
estadedbdbdfe2015-08-05 23:05:0788 std::vector<SkPaint> paints;
Evan Stadecedd7da72015-08-06 22:58:4389 SkRect clip_rect = SkRect::MakeEmpty();
estadeb054c7c2015-12-22 00:25:0390 bool flips_in_rtl = false;
Evan Stadecedd7da72015-08-06 22:58:4391
estadeeaac88b2015-07-30 17:29:3992 for (size_t i = 0; path_elements[i].type != END; i++) {
estade20db97e72015-08-13 17:46:5893 if (paths.empty() || path_elements[i].type == NEW_PATH) {
94 paths.push_back(SkPath());
95 paths.back().setFillType(SkPath::kEvenOdd_FillType);
96
97 paints.push_back(SkPaint());
98 paints.back().setColor(color);
99 paints.back().setAntiAlias(true);
100 paints.back().setStrokeCap(SkPaint::kRound_Cap);
101 }
102
estade804d2472015-08-03 18:54:34103 SkPath& path = paths.back();
Evan Stadecedd7da72015-08-06 22:58:43104 SkPaint& paint = paints.back();
tdandersona2204bc2016-06-16 21:55:17105 CommandType command_type = path_elements[i].type;
106 switch (command_type) {
estade20db97e72015-08-13 17:46:58107 // Handled above.
108 case NEW_PATH:
109 continue;
estadedbdbdfe2015-08-05 23:05:07110
111 case PATH_COLOR_ARGB: {
112 int a = SkScalarFloorToInt(path_elements[++i].arg);
113 int r = SkScalarFloorToInt(path_elements[++i].arg);
114 int g = SkScalarFloorToInt(path_elements[++i].arg);
115 int b = SkScalarFloorToInt(path_elements[++i].arg);
Evan Stadecedd7da72015-08-06 22:58:43116 paint.setColor(SkColorSetARGB(a, r, g, b));
117 break;
118 }
119
estade7ca51e22015-08-31 19:03:33120 case PATH_MODE_CLEAR: {
121 paint.setXfermodeMode(SkXfermode::kClear_Mode);
122 break;
123 };
124
Evan Stadecedd7da72015-08-06 22:58:43125 case STROKE: {
126 paint.setStyle(SkPaint::kStroke_Style);
127 SkScalar width = path_elements[++i].arg;
128 paint.setStrokeWidth(width);
estade804d2472015-08-03 18:54:34129 break;
130 }
131
estadeab97e1d2015-09-21 22:42:22132 case CAP_SQUARE: {
133 paint.setStrokeCap(SkPaint::kSquare_Cap);
134 break;
135 }
136
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39137 case 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.moveTo(x, y);
141 break;
142 }
143
144 case R_MOVE_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.rMoveTo(x, y);
148 break;
149 }
150
estade56b7f3a2016-06-15 21:13:17151 case ARC_TO:
tdandersonaa185fbd2016-06-09 09:18:15152 case R_ARC_TO: {
153 SkScalar rx = path_elements[++i].arg;
154 SkScalar ry = path_elements[++i].arg;
155 SkScalar angle = path_elements[++i].arg;
156 SkScalar large_arc_flag = path_elements[++i].arg;
157 SkScalar arc_sweep_flag = path_elements[++i].arg;
158 SkScalar x = path_elements[++i].arg;
159 SkScalar y = path_elements[++i].arg;
estade56b7f3a2016-06-15 21:13:17160
161 auto path_fn =
tdandersona2204bc2016-06-16 21:55:17162 command_type == ARC_TO
estade56b7f3a2016-06-15 21:13:17163 ? static_cast<void (SkPath::*)(
164 SkScalar, SkScalar, SkScalar, SkPath::ArcSize,
165 SkPath::Direction, SkScalar, SkScalar)>(&SkPath::arcTo)
166 : &SkPath::rArcTo;
167 (path.*path_fn)(
tdandersonaa185fbd2016-06-09 09:18:15168 rx, ry, angle,
169 large_arc_flag ? SkPath::kLarge_ArcSize : SkPath::kSmall_ArcSize,
170 arc_sweep_flag ? SkPath::kCW_Direction : SkPath::kCCW_Direction,
171 x, y);
172 break;
173 }
174
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39175 case LINE_TO: {
estadeeaac88b2015-07-30 17:29:39176 SkScalar x = path_elements[++i].arg;
177 SkScalar y = path_elements[++i].arg;
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39178 path.lineTo(x, y);
179 break;
180 }
181
182 case R_LINE_TO: {
estadeeaac88b2015-07-30 17:29:39183 SkScalar x = path_elements[++i].arg;
184 SkScalar y = path_elements[++i].arg;
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39185 path.rLineTo(x, y);
186 break;
187 }
188
189 case H_LINE_TO: {
190 SkPoint last_point;
191 path.getLastPt(&last_point);
estadeeaac88b2015-07-30 17:29:39192 SkScalar x = path_elements[++i].arg;
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39193 path.lineTo(x, last_point.fY);
194 break;
195 }
196
197 case R_H_LINE_TO: {
estadeeaac88b2015-07-30 17:29:39198 SkScalar x = path_elements[++i].arg;
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39199 path.rLineTo(x, 0);
200 break;
201 }
202
203 case V_LINE_TO: {
204 SkPoint last_point;
205 path.getLastPt(&last_point);
estadeeaac88b2015-07-30 17:29:39206 SkScalar y = path_elements[++i].arg;
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39207 path.lineTo(last_point.fX, y);
208 break;
209 }
210
211 case R_V_LINE_TO: {
estadeeaac88b2015-07-30 17:29:39212 SkScalar y = path_elements[++i].arg;
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39213 path.rLineTo(0, y);
214 break;
215 }
216
estadee1416092015-07-29 18:24:12217 case CUBIC_TO: {
estadeeaac88b2015-07-30 17:29:39218 SkScalar x1 = path_elements[++i].arg;
219 SkScalar y1 = path_elements[++i].arg;
220 SkScalar x2 = path_elements[++i].arg;
221 SkScalar y2 = path_elements[++i].arg;
222 SkScalar x3 = path_elements[++i].arg;
223 SkScalar y3 = path_elements[++i].arg;
estadee1416092015-07-29 18:24:12224 path.cubicTo(x1, y1, x2, y2, x3, y3);
225 break;
226 }
227
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39228 case R_CUBIC_TO: {
estadeeaac88b2015-07-30 17:29:39229 SkScalar x1 = path_elements[++i].arg;
230 SkScalar y1 = path_elements[++i].arg;
231 SkScalar x2 = path_elements[++i].arg;
232 SkScalar y2 = path_elements[++i].arg;
233 SkScalar x3 = path_elements[++i].arg;
234 SkScalar y3 = path_elements[++i].arg;
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39235 path.rCubicTo(x1, y1, x2, y2, x3, y3);
236 break;
237 }
238
estadeeaac88b2015-07-30 17:29:39239 case CIRCLE: {
240 SkScalar x = path_elements[++i].arg;
241 SkScalar y = path_elements[++i].arg;
242 SkScalar r = path_elements[++i].arg;
243 path.addCircle(x, y, r);
244 break;
245 }
246
estade490f73b2015-09-25 01:40:49247 case ROUND_RECT: {
248 SkScalar x = path_elements[++i].arg;
249 SkScalar y = path_elements[++i].arg;
250 SkScalar w = path_elements[++i].arg;
251 SkScalar h = path_elements[++i].arg;
252 SkScalar radius = path_elements[++i].arg;
253 path.addRoundRect(SkRect::MakeXYWH(x, y, w, h), radius, radius);
254 break;
255 }
256
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39257 case CLOSE: {
258 path.close();
259 break;
260 }
261
estade804d2472015-08-03 18:54:34262 case CANVAS_DIMENSIONS: {
263 SkScalar width = path_elements[++i].arg;
264 canvas_size = SkScalarTruncToInt(width);
265 break;
266 }
267
Evan Stadecedd7da72015-08-06 22:58:43268 case CLIP: {
269 SkScalar x = path_elements[++i].arg;
270 SkScalar y = path_elements[++i].arg;
271 SkScalar w = path_elements[++i].arg;
272 SkScalar h = path_elements[++i].arg;
273 clip_rect = SkRect::MakeXYWH(x, y, w, h);
274 break;
275 }
276
estade20db97e72015-08-13 17:46:58277 case DISABLE_AA: {
278 paint.setAntiAlias(false);
279 break;
280 }
281
estadeb054c7c2015-12-22 00:25:03282 case FLIPS_IN_RTL: {
283 flips_in_rtl = true;
284 break;
285 }
286
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39287 case END:
288 NOTREACHED();
289 break;
290 }
291 }
292
estadeb054c7c2015-12-22 00:25:03293 if (flips_in_rtl && base::i18n::IsRTL()) {
294 canvas->Scale(-1, 1);
295 canvas->Translate(gfx::Vector2d(-static_cast<int>(canvas_size), 0));
296 }
297
estade804d2472015-08-03 18:54:34298 if (dip_size != canvas_size) {
299 SkScalar scale = SkIntToScalar(dip_size) / SkIntToScalar(canvas_size);
300 canvas->sk_canvas()->scale(scale, scale);
301 }
302
Evan Stadecedd7da72015-08-06 22:58:43303 if (!clip_rect.isEmpty())
304 canvas->sk_canvas()->clipRect(clip_rect);
305
estadedbdbdfe2015-08-05 23:05:07306 DCHECK_EQ(paints.size(), paths.size());
estade20db97e72015-08-13 17:46:58307 for (size_t i = 0; i < paths.size(); ++i)
estadedbdbdfe2015-08-05 23:05:07308 canvas->DrawPath(paths[i], paints[i]);
estade7ca51e22015-08-31 19:03:33309 canvas->Restore();
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39310}
311
estade6c874602015-08-04 22:45:24312class VectorIconSource : public CanvasImageSource {
313 public:
estade7ca51e22015-08-31 19:03:33314 VectorIconSource(VectorIconId id,
315 size_t dip_size,
316 SkColor color,
317 VectorIconId badge_id)
estade6c874602015-08-04 22:45:24318 : CanvasImageSource(
319 gfx::Size(static_cast<int>(dip_size), static_cast<int>(dip_size)),
320 false),
321 id_(id),
estade7ca51e22015-08-31 19:03:33322 color_(color),
323 badge_id_(badge_id) {}
estade6c874602015-08-04 22:45:24324
325 VectorIconSource(const std::string& definition,
326 size_t dip_size,
327 SkColor color)
328 : CanvasImageSource(
329 gfx::Size(static_cast<int>(dip_size), static_cast<int>(dip_size)),
330 false),
331 id_(VectorIconId::VECTOR_ICON_NONE),
332 path_(PathFromSource(definition)),
estade7ca51e22015-08-31 19:03:33333 color_(color),
334 badge_id_(VectorIconId::VECTOR_ICON_NONE) {}
estade6c874602015-08-04 22:45:24335
336 ~VectorIconSource() override {}
337
338 // CanvasImageSource:
pkasting0c287a32016-03-30 23:04:14339 bool HasRepresentationAtAllScales() const override {
340 return id_ != VectorIconId::VECTOR_ICON_NONE;
341 }
342
estade6c874602015-08-04 22:45:24343 void Draw(gfx::Canvas* canvas) override {
estade7ca51e22015-08-31 19:03:33344 if (path_.empty()) {
estade6c874602015-08-04 22:45:24345 PaintVectorIcon(canvas, id_, size_.width(), color_);
estade7ca51e22015-08-31 19:03:33346 if (badge_id_ != VectorIconId::VECTOR_ICON_NONE)
347 PaintVectorIcon(canvas, badge_id_, size_.width(), color_);
348 } else {
estade6c874602015-08-04 22:45:24349 PaintPath(canvas, path_.data(), size_.width(), color_);
estade7ca51e22015-08-31 19:03:33350 }
estade6c874602015-08-04 22:45:24351 }
352
353 private:
354 const VectorIconId id_;
355 const std::vector<PathElement> path_;
356 const SkColor color_;
estade7ca51e22015-08-31 19:03:33357 const VectorIconId badge_id_;
estade6c874602015-08-04 22:45:24358
359 DISALLOW_COPY_AND_ASSIGN(VectorIconSource);
360};
361
362// This class caches vector icons (as ImageSkia) so they don't have to be drawn
363// more than once. This also guarantees the backing data for the images returned
364// by CreateVectorIcon will persist in memory until program termination.
365class VectorIconCache {
366 public:
367 VectorIconCache() {}
368 ~VectorIconCache() {}
369
estade7ca51e22015-08-31 19:03:33370 ImageSkia GetOrCreateIcon(VectorIconId id,
371 size_t dip_size,
372 SkColor color,
373 VectorIconId badge_id) {
374 IconDescription description(id, dip_size, color, badge_id);
estade6c874602015-08-04 22:45:24375 auto iter = images_.find(description);
376 if (iter != images_.end())
377 return iter->second;
378
379 ImageSkia icon(
estade7ca51e22015-08-31 19:03:33380 new VectorIconSource(id, dip_size, color, badge_id),
estade6c874602015-08-04 22:45:24381 gfx::Size(static_cast<int>(dip_size), static_cast<int>(dip_size)));
382 images_.insert(std::make_pair(description, icon));
383 return icon;
384 }
385
386 private:
387 struct IconDescription {
estade7ca51e22015-08-31 19:03:33388 IconDescription(VectorIconId id,
389 size_t dip_size,
390 SkColor color,
391 VectorIconId badge_id)
392 : id(id), dip_size(dip_size), color(color), badge_id(badge_id) {}
estade6c874602015-08-04 22:45:24393
394 bool operator<(const IconDescription& other) const {
jsbell7faa0862015-11-20 01:26:56395 return std::tie(id, dip_size, color, badge_id) <
396 std::tie(other.id, other.dip_size, other.color, other.badge_id);
estade6c874602015-08-04 22:45:24397 }
398
399 VectorIconId id;
400 size_t dip_size;
401 SkColor color;
estade7ca51e22015-08-31 19:03:33402 VectorIconId badge_id;
estade6c874602015-08-04 22:45:24403 };
404
405 std::map<IconDescription, ImageSkia> images_;
406
407 DISALLOW_COPY_AND_ASSIGN(VectorIconCache);
408};
409
410static base::LazyInstance<VectorIconCache> g_icon_cache =
411 LAZY_INSTANCE_INITIALIZER;
412
413} // namespace
414
415void PaintVectorIcon(Canvas* canvas,
416 VectorIconId id,
417 size_t dip_size,
418 SkColor color) {
419 DCHECK(VectorIconId::VECTOR_ICON_NONE != id);
Evan Stadecedd7da72015-08-06 22:58:43420 const PathElement* path = canvas->image_scale() == 1.f
421 ? GetPathForVectorIconAt1xScale(id)
422 : GetPathForVectorIcon(id);
423 PaintPath(canvas, path, dip_size, color);
estade6c874602015-08-04 22:45:24424}
425
estade3b7f55d2016-04-27 00:21:19426ImageSkia CreateVectorIcon(VectorIconId id, SkColor color) {
427 const PathElement* one_x_path = GetPathForVectorIconAt1xScale(id);
428 size_t size = one_x_path[0].type == CANVAS_DIMENSIONS ? one_x_path[1].arg
429 : kReferenceSizeDip;
430 return CreateVectorIcon(id, size, color);
431}
432
estadea204e0c2015-07-22 19:54:31433ImageSkia CreateVectorIcon(VectorIconId id, size_t dip_size, SkColor color) {
estade7ca51e22015-08-31 19:03:33434 return CreateVectorIconWithBadge(id, dip_size, color,
435 VectorIconId::VECTOR_ICON_NONE);
436}
437
438ImageSkia CreateVectorIconWithBadge(VectorIconId id,
439 size_t dip_size,
440 SkColor color,
441 VectorIconId badge_id) {
pkasting30e625e2016-04-11 22:57:33442 return (id == VectorIconId::VECTOR_ICON_NONE)
443 ? gfx::ImageSkia()
444 : g_icon_cache.Get().GetOrCreateIcon(id, dip_size, color,
445 badge_id);
estadea204e0c2015-07-22 19:54:31446}
447
estade6c874602015-08-04 22:45:24448ImageSkia CreateVectorIconFromSource(const std::string& source,
449 size_t dip_size,
450 SkColor color) {
451 return ImageSkia(
452 new VectorIconSource(source, dip_size, color),
453 gfx::Size(static_cast<int>(dip_size), static_cast<int>(dip_size)));
454}
455
Mattias Nisslerb1fdeb5a2015-07-09 12:10:39456} // namespace gfx