1 // Copyright 2016 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 "third_party/blink/renderer/platform/graphics/placeholder_image.h"
6
7 #include <utility>
8
9 #include "base/stl_util.h"
10 #include "third_party/blink/public/resources/grit/blink_image_resources.h"
11 #include "third_party/blink/public/strings/grit/blink_strings.h"
12 #include "third_party/blink/renderer/platform/fonts/font.h"
13 #include "third_party/blink/renderer/platform/fonts/font_description.h"
14 #include "third_party/blink/renderer/platform/fonts/font_family.h"
15 #include "third_party/blink/renderer/platform/fonts/font_selection_types.h"
16 #include "third_party/blink/renderer/platform/fonts/text_run_paint_info.h"
17 #include "third_party/blink/renderer/platform/geometry/float_point.h"
18 #include "third_party/blink/renderer/platform/geometry/float_rect.h"
19 #include "third_party/blink/renderer/platform/geometry/int_point.h"
20 #include "third_party/blink/renderer/platform/geometry/int_rect.h"
21 #include "third_party/blink/renderer/platform/graphics/bitmap_image.h"
22 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
23 #include "third_party/blink/renderer/platform/graphics/image_observer.h"
24 #include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h"
25 #include "third_party/blink/renderer/platform/graphics/paint/paint_flags.h"
26 #include "third_party/blink/renderer/platform/graphics/paint/paint_record.h"
27 #include "third_party/blink/renderer/platform/graphics/paint/paint_recorder.h"
28 #include "third_party/blink/renderer/platform/text/platform_locale.h"
29 #include "third_party/blink/renderer/platform/text/text_run.h"
30 #include "third_party/blink/renderer/platform/wtf/ref_counted.h"
31 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
32 #include "third_party/skia/include/core/SkColor.h"
33 #include "third_party/skia/include/core/SkRect.h"
34 #include "third_party/skia/include/core/SkSize.h"
35
36 namespace blink {
37
38 namespace {
39
40 // Placeholder image visual specifications:
41 // https://docs.google.com/document/d/1BHeA1azbgCdZgCnr16VN2g7A9MHPQ_dwKn5szh8evMQ/edit
42
43 constexpr int kIconWidth = 24;
44 constexpr int kIconHeight = 24;
45 constexpr int kFeaturePaddingX = 8;
46 constexpr int kIconPaddingY = 5;
47 constexpr int kPaddingBetweenIconAndText = 2;
48 constexpr int kTextPaddingY = 9;
49
50 constexpr int kFontSize = 14;
51
DrawIcon(cc::PaintCanvas * canvas,const PaintFlags & flags,float x,float y,float scale_factor)52 void DrawIcon(cc::PaintCanvas* canvas,
53 const PaintFlags& flags,
54 float x,
55 float y,
56 float scale_factor) {
57 // Note that |icon_image| will be a 0x0 image when running
58 // blink_platform_unittests.
59 DEFINE_STATIC_REF(Image, icon_image,
60 (Image::LoadPlatformResource(IDR_PLACEHOLDER_ICON)));
61
62 // Note that the |icon_image| is not scaled according to dest_rect / src_rect,
63 // and is always drawn at the same size. This is so that placeholder icons are
64 // visible (e.g. when replacing a large image that's scaled down to a small
65 // area) and so that all placeholder images on the same page look consistent.
66 canvas->drawImageRect(
67 icon_image->PaintImageForCurrentFrame(),
68 IntRect(IntPoint::Zero(), icon_image->Size()),
69 FloatRect(x, y, scale_factor * kIconWidth, scale_factor * kIconHeight),
70 &flags, cc::PaintCanvas::kFast_SrcRectConstraint);
71 }
72
DrawCenteredIcon(cc::PaintCanvas * canvas,const PaintFlags & flags,const FloatRect & dest_rect,float scale_factor)73 void DrawCenteredIcon(cc::PaintCanvas* canvas,
74 const PaintFlags& flags,
75 const FloatRect& dest_rect,
76 float scale_factor) {
77 DrawIcon(
78 canvas, flags,
79 dest_rect.X() + (dest_rect.Width() - scale_factor * kIconWidth) / 2.0f,
80 dest_rect.Y() + (dest_rect.Height() - scale_factor * kIconHeight) / 2.0f,
81 scale_factor);
82 }
83
CreatePlaceholderFontDescription(float scale_factor)84 FontDescription CreatePlaceholderFontDescription(float scale_factor) {
85 FontDescription description;
86 description.FirstFamily().SetFamily("Roboto");
87
88 scoped_refptr<SharedFontFamily> helvetica_neue = SharedFontFamily::Create();
89 helvetica_neue->SetFamily("Helvetica Neue");
90 scoped_refptr<SharedFontFamily> helvetica = SharedFontFamily::Create();
91 helvetica->SetFamily("Helvetica");
92 scoped_refptr<SharedFontFamily> arial = SharedFontFamily::Create();
93 arial->SetFamily("Arial");
94
95 helvetica->AppendFamily(std::move(arial));
96 helvetica_neue->AppendFamily(std::move(helvetica));
97 description.FirstFamily().AppendFamily(std::move(helvetica_neue));
98
99 description.SetGenericFamily(FontDescription::kSansSerifFamily);
100 description.SetComputedSize(scale_factor * kFontSize);
101 description.SetWeight(FontSelectionValue(500));
102
103 return description;
104 }
105
106 // Return a byte quantity as a string in a localized human-readable format,
107 // suitable for being shown on a placeholder image to indicate the full original
108 // size of the resource.
109 //
110 // Ex: FormatOriginalResourceSizeBytes(100) => "1 KB"
111 // Ex: FormatOriginalResourceSizeBytes(102401) => "100 KB"
112 // Ex: FormatOriginalResourceSizeBytes(1740800) => "1.7 MB"
113 //
114 // See the placeholder image number format specifications for more info:
115 // https://docs.google.com/document/d/1BHeA1azbgCdZgCnr16VN2g7A9MHPQ_dwKn5szh8evMQ/edit#heading=h.d135l9z7tn0a
FormatOriginalResourceSizeBytes(int64_t bytes)116 String FormatOriginalResourceSizeBytes(int64_t bytes) {
117 DCHECK_LT(0, bytes);
118
119 static constexpr int kUnitsResourceIds[] = {
120 IDS_UNITS_KIBIBYTES, IDS_UNITS_MEBIBYTES, IDS_UNITS_GIBIBYTES,
121 IDS_UNITS_TEBIBYTES, IDS_UNITS_PEBIBYTES};
122
123 // Start with KB. The formatted text will be at least "1 KB", with any smaller
124 // amounts being rounded up to "1 KB".
125 const int* units = kUnitsResourceIds;
126 int64_t denomenator = 1024;
127
128 // Find the smallest unit that can represent |bytes| in 3 digits or less.
129 // Round up to the next higher unit if possible when it would take 4 digits to
130 // display the amount, e.g. 1000 KB will be rounded up to 1 MB.
131 for (; units < kUnitsResourceIds + (base::size(kUnitsResourceIds) - 1) &&
132 bytes >= denomenator * 1000;
133 ++units, denomenator *= 1024) {
134 }
135
136 String numeric_string;
137 if (bytes < denomenator) {
138 // Round up to 1.
139 numeric_string = String::Number(1);
140 } else if (units != kUnitsResourceIds && bytes < denomenator * 10) {
141 // For amounts between 1 and 10 units and larger than 1 MB, allow up to one
142 // fractional digit.
143 numeric_string = String::Number(
144 static_cast<double>(bytes) / static_cast<double>(denomenator), 2);
145 } else {
146 numeric_string = String::Number(bytes / denomenator);
147 }
148
149 Locale& locale = Locale::DefaultLocale();
150 // Locale::QueryString() will return an empty string if the embedder hasn't
151 // defined the string resources for the units, which will cause the
152 // PlaceholderImage to not show any text.
153 return locale.QueryString(*units,
154 locale.ConvertToLocalizedNumber(numeric_string));
155 }
156
157 } // namespace
158
159 // A simple RefCounted wrapper around a Font, so that multiple PlaceholderImages
160 // can share the same Font.
161 class PlaceholderImage::SharedFont : public RefCounted<SharedFont> {
162 public:
GetOrCreateInstance(float scale_factor)163 static scoped_refptr<SharedFont> GetOrCreateInstance(float scale_factor) {
164 if (g_instance_) {
165 scoped_refptr<SharedFont> shared_font(g_instance_);
166 shared_font->MaybeUpdateForScaleFactor(scale_factor);
167 return shared_font;
168 }
169
170 scoped_refptr<SharedFont> shared_font =
171 base::MakeRefCounted<SharedFont>(scale_factor);
172 g_instance_ = shared_font.get();
173 return shared_font;
174 }
175
176 // This constructor is public so that base::MakeRefCounted() can call it.
SharedFont(float scale_factor)177 explicit SharedFont(float scale_factor)
178 : font_(CreatePlaceholderFontDescription(scale_factor)),
179 scale_factor_(scale_factor) {
180 }
181
~SharedFont()182 ~SharedFont() {
183 DCHECK_EQ(this, g_instance_);
184 g_instance_ = nullptr;
185 }
186
MaybeUpdateForScaleFactor(float scale_factor)187 void MaybeUpdateForScaleFactor(float scale_factor) {
188 if (scale_factor_ == scale_factor)
189 return;
190
191 scale_factor_ = scale_factor;
192 font_ = Font(CreatePlaceholderFontDescription(scale_factor_));
193 }
194
font() const195 const Font& font() const { return font_; }
196
197 private:
198 static SharedFont* g_instance_;
199
200 Font font_;
201 float scale_factor_;
202 };
203
204 // static
205 PlaceholderImage::SharedFont* PlaceholderImage::SharedFont::g_instance_ =
206 nullptr;
207
PlaceholderImage(ImageObserver * observer,const IntSize & size,int64_t original_resource_size,bool is_lazy_image)208 PlaceholderImage::PlaceholderImage(ImageObserver* observer,
209 const IntSize& size,
210 int64_t original_resource_size,
211 bool is_lazy_image)
212 : Image(observer),
213 size_(size),
214 text_(original_resource_size <= 0
215 ? String()
216 : FormatOriginalResourceSizeBytes(original_resource_size)),
217 is_lazy_image_(is_lazy_image),
218 paint_record_content_id_(-1) {}
219
220 PlaceholderImage::~PlaceholderImage() = default;
221
Size() const222 IntSize PlaceholderImage::Size() const {
223 return size_;
224 }
225
IsPlaceholderImage() const226 bool PlaceholderImage::IsPlaceholderImage() const {
227 return true;
228 }
229
CurrentFrameHasSingleSecurityOrigin() const230 bool PlaceholderImage::CurrentFrameHasSingleSecurityOrigin() const {
231 return true;
232 }
233
CurrentFrameKnownToBeOpaque()234 bool PlaceholderImage::CurrentFrameKnownToBeOpaque() {
235 // Placeholder images are translucent.
236 return false;
237 }
238
PaintImageForCurrentFrame()239 PaintImage PlaceholderImage::PaintImageForCurrentFrame() {
240 auto builder = CreatePaintImageBuilder().set_completion_state(
241 PaintImage::CompletionState::DONE);
242
243 const IntRect dest_rect(0, 0, size_.Width(), size_.Height());
244 if (paint_record_for_current_frame_) {
245 return builder
246 .set_paint_record(paint_record_for_current_frame_, dest_rect,
247 paint_record_content_id_)
248 .TakePaintImage();
249 }
250
251 PaintRecorder paint_recorder;
252 Draw(paint_recorder.beginRecording(FloatRect(dest_rect)), PaintFlags(),
253 FloatRect(dest_rect), FloatRect(dest_rect), kRespectImageOrientation,
254 kClampImageToSourceRect, kSyncDecode);
255
256 paint_record_for_current_frame_ = paint_recorder.finishRecordingAsPicture();
257 paint_record_content_id_ = PaintImage::GetNextContentId();
258 return builder
259 .set_paint_record(paint_record_for_current_frame_, dest_rect,
260 paint_record_content_id_)
261 .TakePaintImage();
262 }
263
SetIconAndTextScaleFactor(float icon_and_text_scale_factor)264 void PlaceholderImage::SetIconAndTextScaleFactor(
265 float icon_and_text_scale_factor) {
266 if (icon_and_text_scale_factor_ == icon_and_text_scale_factor)
267 return;
268 icon_and_text_scale_factor_ = icon_and_text_scale_factor;
269 cached_text_width_.reset();
270 paint_record_for_current_frame_.reset();
271 }
272
Draw(cc::PaintCanvas * canvas,const PaintFlags & base_flags,const FloatRect & dest_rect,const FloatRect & src_rect,RespectImageOrientationEnum respect_orientation,ImageClampingMode image_clamping_mode,ImageDecodingMode decode_mode)273 void PlaceholderImage::Draw(cc::PaintCanvas* canvas,
274 const PaintFlags& base_flags,
275 const FloatRect& dest_rect,
276 const FloatRect& src_rect,
277 RespectImageOrientationEnum respect_orientation,
278 ImageClampingMode image_clamping_mode,
279 ImageDecodingMode decode_mode) {
280 if (!src_rect.Intersects(FloatRect(0.0f, 0.0f,
281 static_cast<float>(size_.Width()),
282 static_cast<float>(size_.Height())))) {
283 return;
284 }
285 if (is_lazy_image_) {
286 // Keep the image without any color and text decorations.
287 return;
288 }
289
290 PaintFlags flags(base_flags);
291 flags.setStyle(PaintFlags::kFill_Style);
292 flags.setColor(SkColorSetARGB(0x80, 0xD9, 0xD9, 0xD9));
293 canvas->drawRect(dest_rect, flags);
294
295 if (dest_rect.Width() <
296 icon_and_text_scale_factor_ * (kIconWidth + 2 * kFeaturePaddingX) ||
297 dest_rect.Height() <
298 icon_and_text_scale_factor_ * (kIconHeight + 2 * kIconPaddingY)) {
299 return;
300 }
301
302 if (text_.IsEmpty()) {
303 DrawCenteredIcon(canvas, base_flags, dest_rect,
304 icon_and_text_scale_factor_);
305 return;
306 }
307
308 if (!shared_font_)
309 shared_font_ = SharedFont::GetOrCreateInstance(icon_and_text_scale_factor_);
310 else
311 shared_font_->MaybeUpdateForScaleFactor(icon_and_text_scale_factor_);
312
313 if (!cached_text_width_.has_value())
314 cached_text_width_ = shared_font_->font().Width(TextRun(text_));
315
316 const float icon_and_text_width =
317 cached_text_width_.value() +
318 icon_and_text_scale_factor_ *
319 (kIconWidth + 2 * kFeaturePaddingX + kPaddingBetweenIconAndText);
320
321 if (dest_rect.Width() < icon_and_text_width) {
322 DrawCenteredIcon(canvas, base_flags, dest_rect,
323 icon_and_text_scale_factor_);
324 return;
325 }
326
327 const float feature_x =
328 dest_rect.X() + (dest_rect.Width() - icon_and_text_width) / 2.0f;
329 const float feature_y =
330 dest_rect.Y() +
331 (dest_rect.Height() -
332 icon_and_text_scale_factor_ * (kIconHeight + 2 * kIconPaddingY)) /
333 2.0f;
334
335 float icon_x, text_x;
336 if (Locale::DefaultLocale().IsRTL()) {
337 icon_x = feature_x + cached_text_width_.value() +
338 icon_and_text_scale_factor_ *
339 (kFeaturePaddingX + kPaddingBetweenIconAndText);
340 text_x = feature_x + icon_and_text_scale_factor_ * kFeaturePaddingX;
341 } else {
342 icon_x = feature_x + icon_and_text_scale_factor_ * kFeaturePaddingX;
343 text_x = feature_x +
344 icon_and_text_scale_factor_ *
345 (kFeaturePaddingX + kIconWidth + kPaddingBetweenIconAndText);
346 }
347
348 DrawIcon(canvas, base_flags, icon_x,
349 feature_y + icon_and_text_scale_factor_ * kIconPaddingY,
350 icon_and_text_scale_factor_);
351
352 flags.setColor(SkColorSetARGB(0xAB, 0, 0, 0));
353 shared_font_->font().DrawBidiText(
354 canvas, TextRunPaintInfo(TextRun(text_)),
355 FloatPoint(text_x, feature_y + icon_and_text_scale_factor_ *
356 (kTextPaddingY + kFontSize)),
357 Font::kUseFallbackIfFontNotReady, 1.0f, flags);
358 }
359
DrawPattern(GraphicsContext & context,const FloatRect & src_rect,const FloatSize & scale,const FloatPoint & phase,SkBlendMode mode,const FloatRect & dest_rect,const FloatSize & repeat_spacing,RespectImageOrientationEnum respect_orientation)360 void PlaceholderImage::DrawPattern(
361 GraphicsContext& context,
362 const FloatRect& src_rect,
363 const FloatSize& scale,
364 const FloatPoint& phase,
365 SkBlendMode mode,
366 const FloatRect& dest_rect,
367 const FloatSize& repeat_spacing,
368 RespectImageOrientationEnum respect_orientation) {
369 DCHECK(context.Canvas());
370
371 PaintFlags flags = context.FillFlags();
372 flags.setBlendMode(mode);
373
374 // Ignore the pattern specifications and just draw a single placeholder image
375 // over the whole |dest_rect|. This is done in order to prevent repeated icons
376 // from cluttering tiled background images.
377 Draw(context.Canvas(), flags, dest_rect, src_rect, respect_orientation,
378 kClampImageToSourceRect, kUnspecifiedDecode);
379 }
380
DestroyDecodedData()381 void PlaceholderImage::DestroyDecodedData() {
382 paint_record_for_current_frame_.reset();
383 shared_font_ = scoped_refptr<SharedFont>();
384 }
385
SetData(scoped_refptr<SharedBuffer>,bool)386 Image::SizeAvailability PlaceholderImage::SetData(scoped_refptr<SharedBuffer>,
387 bool) {
388 return Image::kSizeAvailable;
389 }
390
GetFontForTesting() const391 const Font* PlaceholderImage::GetFontForTesting() const {
392 return shared_font_ ? &shared_font_->font() : nullptr;
393 }
394
395 } // namespace blink
396