OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2014 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 "chrome/browser/android/thumbnail/thumbnail_cache.h" | |
6 | |
7 #include <algorithm> | |
8 #include <cmath> | |
9 | |
10 #include "base/file_util.h" | |
11 #include "base/files/file.h" | |
12 #include "base/files/file_enumerator.h" | |
13 #include "base/files/file_path.h" | |
14 #include "base/strings/string_number_conversions.h" | |
15 #include "base/time/time.h" | |
16 #include "content/public/browser/android/ui_resource_provider.h" | |
17 #include "content/public/browser/browser_thread.h" | |
18 #include "third_party/skia/include/core/SkBitmap.h" | |
19 #include "third_party/skia/include/core/SkCanvas.h" | |
20 #include "ui/gfx/geometry/size_conversions.h" | |
21 | |
22 namespace { | |
23 | |
24 const bool kDropCachedNTPOnLowMemory = true; | |
25 const float kApproximationScaleFactor = 4.f; | |
26 const base::TimeDelta kCaptureMinRequestTimeMs( | |
27 base::TimeDelta::FromMilliseconds(1000)); | |
28 | |
29 } // anonymous namespace | |
30 | |
31 ThumbnailCache::ThumbnailCache(const std::string& disk_cache_path_str, | |
32 size_t default_cache_size, | |
33 size_t approximation_cache_size, | |
34 size_t compression_queue_max_size, | |
35 size_t write_queue_max_size, | |
36 bool use_approximation_thumbnail) | |
37 : disk_cache_path_(disk_cache_path_str), | |
38 compression_queue_max_size_(compression_queue_max_size), | |
39 write_queue_max_size_(write_queue_max_size), | |
40 use_approximation_thumbnail_(use_approximation_thumbnail), | |
41 cache_(default_cache_size), | |
42 approximation_cache_(approximation_cache_size), | |
43 ui_resource_provider_(NULL), | |
44 compression_thread_("thumbnail_compression"), | |
45 weak_factory_(this) { | |
46 compression_thread_.Start(); | |
47 } | |
48 | |
49 ThumbnailCache::~ThumbnailCache() { | |
50 compression_thread_.Stop(); | |
51 SetUIResourceProvider(NULL); | |
52 } | |
53 | |
54 void ThumbnailCache::SetUIResourceProvider( | |
55 content::UIResourceProvider* ui_resource_provider) { | |
56 if (ui_resource_provider_ == ui_resource_provider) | |
57 return; | |
58 | |
59 approximation_cache_.Clear(); | |
60 cache_.Clear(); | |
61 | |
62 ui_resource_provider_ = ui_resource_provider; | |
63 } | |
64 | |
65 void ThumbnailCache::AddThumbnailCacheObserver( | |
66 ThumbnailCacheObserver* observer) { | |
67 if (!observers_.HasObserver(observer)) | |
68 observers_.AddObserver(observer); | |
69 } | |
70 | |
71 void ThumbnailCache::RemoveThumbnailCacheObserver( | |
72 ThumbnailCacheObserver* observer) { | |
73 if (observers_.HasObserver(observer)) | |
74 observers_.RemoveObserver(observer); | |
75 } | |
76 | |
77 void ThumbnailCache::Put(TabId tab_id, | |
78 const SkBitmap& bitmap, | |
79 float thumbnail_scale) { | |
80 if (!ui_resource_provider_ || bitmap.empty() || thumbnail_scale <= 0) | |
81 return; | |
82 | |
83 scoped_refptr<ThumbnailImpl> thumbnail = make_scoped_refptr(new ThumbnailImpl( | |
84 tab_id, bitmap, thumbnail_scale, this, ui_resource_provider_)); | |
85 | |
86 RemoveFromQueues(tab_id); | |
87 MakeSpaceForNewItemIfNecessary(tab_id); | |
88 cache_.Put(tab_id, thumbnail); | |
89 | |
90 if (use_approximation_thumbnail_) { | |
91 std::pair<SkBitmap, float> approximation = | |
92 CreateApproximation(bitmap, thumbnail_scale); | |
93 scoped_refptr<ThumbnailImpl> approx_thumbnail = | |
94 make_scoped_refptr(new ThumbnailImpl(tab_id, | |
95 approximation.first, | |
96 approximation.second, | |
97 this, | |
98 ui_resource_provider_)); | |
99 approximation_cache_.Put(tab_id, approx_thumbnail); | |
100 } | |
101 | |
102 CompressThumbnailIfNecessary(thumbnail); | |
103 } | |
104 | |
105 void ThumbnailCache::Remove(TabId tab_id) { | |
106 cache_.Remove(tab_id); | |
107 approximation_cache_.Remove(tab_id); | |
108 thumbnail_meta_data_.erase(tab_id); | |
109 RemoveFileFromDisk(GetFilePath(tab_id)); | |
110 RemoveFromQueues(tab_id); | |
111 } | |
112 | |
113 scoped_refptr<Thumbnail> ThumbnailCache::Get(TabId tab_id) { | |
114 scoped_refptr<ThumbnailImpl> thumbnail = cache_.Get(tab_id); | |
115 if (!thumbnail) | |
116 thumbnail = approximation_cache_.Get(tab_id); | |
117 | |
118 // If we are trying to find a thumbnail that is not in memory cache and yet is | |
119 // visible, then try paging it in from disk. | |
120 if (!thumbnail && | |
121 std::find(visible_ids_.begin(), visible_ids_.end(), tab_id) != | |
122 visible_ids_.end() && | |
123 std::find(read_queue_.begin(), read_queue_.end(), tab_id) == | |
124 read_queue_.end()) { | |
125 read_queue_.push_back(tab_id); | |
126 ReadNextThumbnail(); | |
127 } | |
128 return thumbnail; | |
129 } | |
130 | |
131 void ThumbnailCache::RemoveFromDiskAtAndAboveId(TabId min_id) { | |
132 base::Closure remove_task = | |
133 base::Bind(&ThumbnailCache::RemoveFromDiskAtAndAboveIdTask, | |
134 disk_cache_path_, | |
135 min_id); | |
136 content::BrowserThread::PostTask( | |
137 content::BrowserThread::FILE, FROM_HERE, remove_task); | |
138 } | |
139 | |
140 void ThumbnailCache::InvalidateThumbnailIfChanged(TabId tab_id, | |
141 const GURL& url) { | |
142 ThumbnailMetaDataMap::iterator meta_data_iter = | |
143 thumbnail_meta_data_.find(tab_id); | |
144 if (meta_data_iter == thumbnail_meta_data_.end()) { | |
145 thumbnail_meta_data_[tab_id] = ThumbnailMetaData(base::Time(), url); | |
146 } else if (meta_data_iter->second.url() != url) { | |
147 Remove(tab_id); | |
148 } | |
149 } | |
150 | |
151 bool ThumbnailCache::CheckAndUpdateThumbnailMetaData(TabId tab_id, | |
152 const GURL& url) { | |
153 ThumbnailMetaDataMap::iterator meta_data_iter = | |
154 thumbnail_meta_data_.find(tab_id); | |
155 if (meta_data_iter != thumbnail_meta_data_.end() && | |
156 meta_data_iter->second.url() == url && | |
157 (CurrentTime() - meta_data_iter->second.capture_time()) < | |
158 kCaptureMinRequestTimeMs) { | |
159 return false; | |
160 } | |
161 | |
162 thumbnail_meta_data_[tab_id] = ThumbnailMetaData(CurrentTime(), url); | |
163 return true; | |
164 } | |
165 | |
166 void ThumbnailCache::UpdateVisibleIds(const TabIdList& priority) { | |
167 if (priority.empty()) { | |
168 visible_ids_.clear(); | |
169 return; | |
170 } | |
171 | |
172 size_t ids_size = std::min(priority.size(), cache_.MaximumCacheSize()); | |
173 if (visible_ids_.size() == ids_size) { | |
174 // Early out if called with the same input as last time (We only care | |
175 // about the first mCache.MaximumCacheSize() entries). | |
176 bool lists_differ = false; | |
177 for (TabIdList::const_iterator visible_iter = visible_ids_.begin(), | |
David Trainor- moved to gerrit
2014/06/17 08:12:11
A while loop might look a bit cleaner.
powei
2014/06/19 23:05:59
Done.
| |
178 priority_iter = priority.begin(); | |
179 visible_iter != visible_ids_.end() && priority_iter != priority.end(); | |
180 visible_iter++, priority_iter++) { | |
181 if (*priority_iter != *visible_iter) { | |
182 lists_differ = true; | |
183 break; | |
184 } | |
185 } | |
186 if (!lists_differ) | |
187 return; | |
188 } | |
189 | |
190 read_queue_.clear(); | |
191 visible_ids_.clear(); | |
192 size_t count = 0; | |
193 for (TabIdList::const_iterator iter = priority.begin(); | |
194 iter != priority.end() && count < ids_size; | |
195 iter++, count++) { | |
196 TabId tab_id = *iter; | |
197 visible_ids_.push_back(tab_id); | |
198 if (!cache_.Get(tab_id) && | |
199 std::find(read_queue_.begin(), read_queue_.end(), tab_id) == | |
David Trainor- moved to gerrit
2014/06/17 08:12:11
Is this to check if the same id is in the list twi
powei
2014/06/19 23:05:59
Yes it is. We probably don't need this if we're s
| |
200 read_queue_.end()) { | |
201 read_queue_.push_back(tab_id); | |
202 } | |
203 } | |
204 | |
205 ReadNextThumbnail(); | |
206 } | |
207 | |
208 void ThumbnailCache::RemoveFileFromDisk(const base::FilePath& file_path) { | |
209 base::Closure task = | |
210 base::Bind(&ThumbnailCache::RemoveFileFromDiskTask, file_path); | |
211 content::BrowserThread::PostTask( | |
212 content::BrowserThread::FILE, FROM_HERE, task); | |
213 } | |
214 | |
215 void ThumbnailCache::RemoveFileFromDiskTask(const base::FilePath& file_path) { | |
216 base::DeleteFile(file_path, false); | |
217 } | |
218 | |
219 void ThumbnailCache::RemoveFromDiskAtAndAboveIdTask( | |
220 const base::FilePath& dir_path, | |
221 TabId min_id) { | |
222 base::FileEnumerator enumerator(dir_path, false, base::FileEnumerator::FILES); | |
223 while (true) { | |
224 base::FilePath path = enumerator.Next(); | |
225 if (path.empty()) | |
226 break; | |
227 base::FileEnumerator::FileInfo info = enumerator.GetInfo(); | |
228 TabId tab_id; | |
229 bool success = base::StringToInt(info.GetName().value(), &tab_id); | |
230 if (success && tab_id >= min_id) | |
231 base::DeleteFile(path, false); | |
232 } | |
233 } | |
234 | |
235 void ThumbnailCache::WriteThumbnailIfNecessary( | |
236 scoped_refptr<ThumbnailImpl> thumbnail) { | |
237 RemoveDuplicateIdsFromQueueHelper(write_queue_, thumbnail); | |
238 if (write_queue_.size() > write_queue_max_size_) | |
239 write_queue_.pop_front(); | |
240 | |
241 write_queue_.push_back(thumbnail); | |
242 WriteNextThumbnail(); | |
243 } | |
244 | |
245 void ThumbnailCache::CompressThumbnailIfNecessary( | |
246 scoped_refptr<ThumbnailImpl> thumbnail) { | |
247 RemoveDuplicateIdsFromQueueHelper(compression_queue_, thumbnail); | |
248 if (compression_queue_.size() > compression_queue_max_size_) | |
249 compression_queue_.pop_front(); | |
250 | |
251 compression_queue_.push_back(thumbnail); | |
252 CompressNextThumbnail(); | |
253 } | |
254 | |
255 void ThumbnailCache::MakeSpaceForNewItemIfNecessary(TabId tab_id) { | |
256 if (cache_.Get(tab_id) || | |
257 std::find(visible_ids_.begin(), visible_ids_.end(), tab_id) == | |
258 visible_ids_.end() || | |
259 cache_.size() < cache_.MaximumCacheSize()) { | |
260 return; | |
261 } | |
262 | |
263 TabId key_to_remove; | |
264 bool found_key_to_remove = false; | |
265 | |
266 // 1. Find a cached item not in this list | |
267 for (ExpiringThumbnailCache::iterator iter = cache_.begin(); | |
268 iter != cache_.end(); | |
269 iter++) { | |
270 if (std::find(visible_ids_.begin(), visible_ids_.end(), iter->first) == | |
271 visible_ids_.end()) { | |
272 key_to_remove = iter->first; | |
273 found_key_to_remove = true; | |
274 break; | |
275 } | |
276 } | |
277 | |
278 if (!found_key_to_remove) { | |
279 // 2. Find the least important id we can remove. | |
280 for (TabIdList::reverse_iterator riter = visible_ids_.rbegin(); | |
281 riter != visible_ids_.rend(); | |
282 riter++) { | |
283 if (cache_.Get(*riter)) { | |
284 key_to_remove = *riter; | |
285 break; | |
286 found_key_to_remove = true; | |
287 } | |
288 } | |
289 } | |
290 | |
291 if (found_key_to_remove) | |
292 cache_.Remove(key_to_remove); | |
293 } | |
294 | |
295 void ThumbnailCache::RemoveFromQueues(TabId tab_id) { | |
296 TabIdList::iterator read_iter = | |
297 std::find(read_queue_.begin(), read_queue_.end(), tab_id); | |
298 if (read_iter != read_queue_.end()) | |
299 read_queue_.erase(read_iter); | |
300 | |
301 ThumbnailQueue new_write_queue; | |
302 for (ThumbnailQueue::iterator iter = write_queue_.begin(); | |
303 iter != write_queue_.end(); | |
304 iter++) { | |
305 scoped_refptr<ThumbnailImpl> write_thumbnail = *iter; | |
306 if (write_thumbnail->tab_id() != tab_id) | |
David Trainor- moved to gerrit
2014/06/17 08:12:11
Why not if (==) iter.remove()? Same below and for
powei
2014/06/19 23:05:59
Done. changed to "iter = write_queue_.erase(iter);
| |
307 new_write_queue.push_back(write_thumbnail); | |
308 } | |
309 write_queue_ = new_write_queue; | |
310 | |
311 ThumbnailQueue new_compression_queue; | |
312 for (ThumbnailQueue::iterator iter = compression_queue_.begin(); | |
313 iter != compression_queue_.end(); | |
314 iter++) { | |
315 scoped_refptr<ThumbnailImpl> compress_thumbnail = *iter; | |
316 if (compress_thumbnail->tab_id() != tab_id) | |
317 new_compression_queue.push_back(compress_thumbnail); | |
318 } | |
319 compression_queue_ = new_compression_queue; | |
320 } | |
321 | |
322 bool ThumbnailCache::RemoveDuplicateIdsFromQueueHelper( | |
323 ThumbnailQueue& queue, | |
324 scoped_refptr<ThumbnailImpl> thumbnail) { | |
325 bool found = false; | |
326 ThumbnailQueue::iterator iter = queue.begin(); | |
327 ThumbnailQueue good_queue; | |
328 for (ThumbnailQueue::iterator iter = queue.begin(); iter != queue.end(); | |
329 iter++) { | |
330 if ((*iter)->tab_id() != thumbnail->tab_id()) { | |
331 good_queue.push_back(thumbnail); | |
332 } else { | |
333 found = true; | |
334 } | |
335 } | |
336 queue = good_queue; | |
337 return found; | |
338 } | |
339 | |
340 void ThumbnailCache::InvalidateCachedThumbnail( | |
341 scoped_refptr<ThumbnailImpl> thumbnail) { | |
342 TabId tab_id = thumbnail->tab_id(); | |
343 if (cache_.Get(tab_id) == thumbnail) | |
344 cache_.Remove(tab_id); | |
345 | |
346 if (approximation_cache_.Get(tab_id) == thumbnail) | |
347 approximation_cache_.Remove(tab_id); | |
348 } | |
349 | |
350 base::FilePath ThumbnailCache::GetFilePath(TabId tab_id) const { | |
351 return disk_cache_path_.Append(base::IntToString(tab_id)); | |
352 } | |
353 | |
354 base::Time ThumbnailCache::CurrentTime() { | |
355 return base::Time::Now(); | |
356 } | |
357 | |
358 void ThumbnailCache::WriteNextThumbnail() { | |
David Trainor- moved to gerrit
2014/06/17 08:12:11
What if we're already waiting for a write to finis
powei
2014/06/19 23:05:59
Are you thinking about how the size of the write_q
David Trainor- moved to gerrit
2014/06/25 07:40:15
I'm more worried that we have a write queue that d
powei
2014/07/07 17:24:51
Agreed offline that we'd keep a count instead of a
| |
359 if (write_queue_.empty()) | |
360 return; | |
361 | |
362 scoped_refptr<ThumbnailImpl> thumbnail = write_queue_.front(); | |
363 write_queue_.pop_front(); | |
364 | |
365 if (!thumbnail->IsValid()) | |
366 return; | |
367 | |
368 scoped_refptr<ThumbnailImpl> in_thumbnail = | |
369 thumbnail->PassDataToNewThumbnail(); | |
David Trainor- moved to gerrit
2014/06/17 08:12:11
Won't this steal the reference from the object in
powei
2014/06/19 23:05:58
The point is to steal the reference. This way we'
David Trainor- moved to gerrit
2014/06/25 07:40:16
But since we're building the ui resource id on dem
powei
2014/07/07 17:24:51
Done. Moved to more explicit passing of ref-counte
| |
370 | |
371 base::Closure write_task = base::Bind(&ThumbnailCache::WriteTask, | |
372 in_thumbnail, | |
373 GetFilePath(thumbnail->tab_id())); | |
374 base::Closure post_write_task = base::Bind( | |
375 &ThumbnailCache::WriteNextThumbnail, weak_factory_.GetWeakPtr()); | |
376 | |
377 content::BrowserThread::PostTaskAndReply( | |
378 content::BrowserThread::FILE, FROM_HERE, write_task, post_write_task); | |
379 } | |
380 | |
381 void ThumbnailCache::WriteTask(scoped_refptr<ThumbnailImpl> thumbnail, | |
382 const base::FilePath& file_path) { | |
383 DCHECK(thumbnail); | |
384 DCHECK(thumbnail->IsValid()); | |
385 | |
386 base::File file(file_path, | |
387 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); | |
388 bool success = ThumbnailImpl::WriteToFile(thumbnail, file); | |
389 file.Close(); | |
390 | |
391 if (!success) | |
392 RemoveFileFromDisk(file_path); | |
David Trainor- moved to gerrit
2014/06/17 08:12:11
Is posting this safe? What if the app dies before
powei
2014/06/19 23:05:59
Done. Good catch. called DeleteFile directly ins
| |
393 } | |
394 | |
395 void ThumbnailCache::ReadNextThumbnail() { | |
396 if (read_queue_.empty()) | |
397 return; | |
398 | |
399 TabId tab_id = read_queue_.front(); | |
400 | |
401 // Create an empty thumbnail to hold the content of the read. | |
402 scoped_refptr<ThumbnailImpl> output_thumbnail = make_scoped_refptr( | |
403 new ThumbnailImpl(tab_id, SkBitmap(), 0.f, this, ui_resource_provider_)); | |
404 | |
405 base::FilePath file_path = GetFilePath(tab_id); | |
406 base::Closure read_task = | |
407 base::Bind(&ThumbnailCache::ReadTask, file_path, output_thumbnail); | |
408 base::Closure post_read_task = base::Bind(&ThumbnailCache::PostReadTask, | |
409 weak_factory_.GetWeakPtr(), | |
410 output_thumbnail); | |
411 content::BrowserThread::PostTaskAndReply( | |
412 content::BrowserThread::FILE, FROM_HERE, read_task, post_read_task); | |
413 } | |
414 | |
415 void ThumbnailCache::ReadTask(const base::FilePath& file_path, | |
416 scoped_refptr<ThumbnailImpl> thumbnail) { | |
417 DCHECK(thumbnail); | |
418 if (!base::PathExists(file_path)) | |
419 return; | |
420 | |
421 base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ); | |
422 DCHECK(file.IsValid()); | |
423 bool success = ThumbnailImpl::ReadFromFile(file, thumbnail); | |
424 file.Close(); | |
425 | |
426 if (!success) | |
427 RemoveFileFromDisk(file_path); | |
428 } | |
429 | |
430 void ThumbnailCache::PostReadTask(scoped_refptr<ThumbnailImpl> thumbnail) { | |
431 DCHECK(thumbnail); | |
432 TabIdList::iterator iter = | |
433 std::find(read_queue_.begin(), read_queue_.end(), thumbnail->tab_id()); | |
434 if (iter == read_queue_.end()) | |
435 return; | |
436 | |
437 read_queue_.erase(iter); | |
438 TabId tab_id = thumbnail->tab_id(); | |
439 MakeSpaceForNewItemIfNecessary(tab_id); | |
440 cache_.Put(tab_id, thumbnail); | |
441 NotifyObserversOfThumbnailRead(tab_id); | |
442 ReadNextThumbnail(); | |
443 } | |
444 | |
445 void ThumbnailCache::CompressNextThumbnail() { | |
David Trainor- moved to gerrit
2014/06/17 08:12:11
Shouldn't this queue up the tasks and only run one
powei
2014/06/19 23:05:59
see comment for write task.
| |
446 if (!compression_queue_.empty()) { | |
447 scoped_refptr<ThumbnailImpl> thumbnail = compression_queue_.front(); | |
448 compression_queue_.pop_front(); | |
449 | |
450 scoped_refptr<ThumbnailImpl> in_thumbnail = | |
451 thumbnail->PassDataToNewThumbnail(); | |
David Trainor- moved to gerrit
2014/06/17 08:12:11
Doesn't this also steal the bitmap/data from the t
powei
2014/06/19 23:05:59
This does steal the reference, and once compressio
David Trainor- moved to gerrit
2014/06/25 07:40:15
Still not sure how this works if we compress befor
| |
452 | |
453 scoped_refptr<ThumbnailImpl> out_thumbnail = | |
454 scoped_refptr<ThumbnailImpl>(new ThumbnailImpl(in_thumbnail->tab_id(), | |
455 SkBitmap(), | |
456 0.f, | |
457 this, | |
458 ui_resource_provider_)); | |
459 base::Closure compression_task = | |
460 base::Bind(&ThumbnailImpl::Compress, in_thumbnail, out_thumbnail); | |
461 base::Closure post_compression_task = | |
462 base::Bind(&ThumbnailCache::PostCompressionTask, | |
463 weak_factory_.GetWeakPtr(), | |
464 out_thumbnail); | |
465 | |
466 DCHECK(compression_thread_.message_loop_proxy()); | |
467 compression_thread_.message_loop_proxy()->PostTaskAndReply( | |
468 FROM_HERE, compression_task, post_compression_task); | |
469 } | |
470 } | |
471 | |
472 void ThumbnailCache::PostCompressionTask( | |
473 scoped_refptr<ThumbnailImpl> compressed_thumbnail) { | |
474 DCHECK(compressed_thumbnail); | |
475 TabId tab_id = compressed_thumbnail->tab_id(); | |
476 cache_.Remove(tab_id); | |
477 if (compressed_thumbnail->IsValid()) { | |
478 cache_.Put(tab_id, compressed_thumbnail); | |
479 WriteThumbnailIfNecessary(compressed_thumbnail); | |
480 } | |
481 CompressNextThumbnail(); | |
482 } | |
483 | |
484 void ThumbnailCache::NotifyObserversOfThumbnailRead(TabId tab_id) { | |
485 FOR_EACH_OBSERVER( | |
486 ThumbnailCacheObserver, observers_, OnFinishedThumbnailRead(tab_id)); | |
487 } | |
488 | |
489 ThumbnailCache::ThumbnailMetaData::ThumbnailMetaData() { | |
490 } | |
491 | |
492 ThumbnailCache::ThumbnailMetaData::ThumbnailMetaData( | |
493 const base::Time& current_time, | |
494 const GURL& url) | |
495 : capture_time_(current_time), url_(url) { | |
496 } | |
497 | |
498 std::pair<SkBitmap, float> ThumbnailCache::CreateApproximation( | |
499 const SkBitmap& bitmap, | |
500 float scale) { | |
501 DCHECK(!bitmap.empty()); | |
502 DCHECK_GT(scale, 0); | |
503 SkAutoLockPixels bitmap_lock(bitmap); | |
504 float new_scale = 1.f / kApproximationScaleFactor; | |
505 | |
506 gfx::Size dst_size = gfx::ToFlooredSize( | |
507 gfx::ScaleSize(gfx::Size(bitmap.width(), bitmap.height()), new_scale)); | |
508 SkBitmap dst_bitmap; | |
509 dst_bitmap.setConfig(bitmap.getConfig(), dst_size.width(), dst_size.height()); | |
510 dst_bitmap.allocPixels(); | |
511 dst_bitmap.eraseColor(0); | |
512 SkAutoLockPixels dst_bitmap_lock(dst_bitmap); | |
513 | |
514 SkCanvas canvas(dst_bitmap); | |
515 canvas.scale(new_scale, new_scale); | |
516 canvas.drawBitmap(bitmap, 0, 0, NULL); | |
517 dst_bitmap.setImmutable(); | |
518 | |
519 return std::make_pair(dst_bitmap, new_scale * scale); | |
520 } | |
OLD | NEW |