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.
5 #include "chrome/browser/android/thumbnail/thumbnail_store.h"
10 #include "base/big_endian.h"
11 #include "base/file_util.h"
12 #include "base/files/file.h"
13 #include "base/files/file_enumerator.h"
14 #include "base/files/file_path.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/threading/worker_pool.h"
17 #include "base/time/time.h"
18 #include "content/public/browser/android/ui_resource_provider.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "third_party/android_opengl/etc1/etc1.h"
21 #include "third_party/skia/include/core/SkBitmap.h"
22 #include "third_party/skia/include/core/SkCanvas.h"
23 #include "third_party/skia/include/core/SkData.h"
24 #include "third_party/skia/include/core/SkMallocPixelRef.h"
25 #include "third_party/skia/include/core/SkPixelRef.h"
26 #include "ui/gfx/android/device_display_info.h"
27 #include "ui/gfx/geometry/size_conversions.h"
31 const float kApproximationScaleFactor = 4.f;
32 const base::TimeDelta kCaptureMinRequestTimeMs(
33 base::TimeDelta::FromMilliseconds(1000));
35 const int kCompressedKey = 0xABABABAB;
36 const int kCurrentExtraVersion = 1;
38 // Indicates whether we prefer to have more free CPU memory over GPU memory.
39 const bool kPreferCPUMemory = true;
41 // TODO(): ETC1 texture sizes should be multiples of four, but some drivers only
42 // allow power-of-two ETC1 textures. Find better work around.
43 size_t NextPowerOfTwo(size_t x) {
53 gfx::Size GetEncodedSize(const gfx::Size& bitmap_size) {
54 DCHECK(!bitmap_size.IsEmpty());
55 return gfx::Size(NextPowerOfTwo(bitmap_size.width()),
56 NextPowerOfTwo(bitmap_size.height()));
60 bool ReadBigEndianFromFile(base::File& file, T* out) {
61 char buffer[sizeof(T)];
62 if (file.ReadAtCurrentPos(buffer, sizeof(T)) != sizeof(T))
64 base::ReadBigEndian(buffer, out);
69 bool WriteBigEndianToFile(base::File& file, T val) {
70 char buffer[sizeof(T)];
71 base::WriteBigEndian(buffer, val);
72 return file.WriteAtCurrentPos(buffer, sizeof(T)) == sizeof(T);
75 bool ReadBigEndianFloatFromFile(base::File& file, float* out) {
76 char buffer[sizeof(float)];
77 if (file.ReadAtCurrentPos(buffer, sizeof(buffer)) != sizeof(buffer))
80 #if defined(ARCH_CPU_LITTLE_ENDIAN)
81 for (size_t i = 0; i < sizeof(float) / 2; i++) {
83 buffer[i] = buffer[sizeof(float) - 1 - i];
84 buffer[sizeof(float) - 1 - i] = tmp;
87 memcpy(out, buffer, sizeof(buffer));
92 bool WriteBigEndianFloatToFile(base::File& file, float val) {
93 char buffer[sizeof(float)];
94 memcpy(buffer, &val, sizeof(buffer));
96 #if defined(ARCH_CPU_LITTLE_ENDIAN)
97 for (size_t i = 0; i < sizeof(float) / 2; i++) {
99 buffer[i] = buffer[sizeof(float) - 1 - i];
100 buffer[sizeof(float) - 1 - i] = tmp;
103 return file.WriteAtCurrentPos(buffer, sizeof(buffer)) == sizeof(buffer);
106 } // anonymous namespace
108 ThumbnailStore::ThumbnailStore(const std::string& disk_cache_path_str,
109 size_t default_cache_size,
110 size_t approximation_cache_size,
111 size_t compression_queue_max_size,
112 size_t write_queue_max_size,
113 bool use_approximation_thumbnail)
114 : disk_cache_path_(disk_cache_path_str),
115 compression_queue_max_size_(compression_queue_max_size),
116 write_queue_max_size_(write_queue_max_size),
117 use_approximation_thumbnail_(use_approximation_thumbnail),
118 compression_tasks_count_(0),
119 write_tasks_count_(0),
120 read_in_progress_(false),
121 cache_(default_cache_size),
122 approximation_cache_(approximation_cache_size),
123 ui_resource_provider_(NULL),
124 weak_factory_(this) {
125 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
128 ThumbnailStore::~ThumbnailStore() {
129 SetUIResourceProvider(NULL);
132 void ThumbnailStore::SetUIResourceProvider(
133 content::UIResourceProvider* ui_resource_provider) {
134 if (ui_resource_provider_ == ui_resource_provider)
137 approximation_cache_.Clear();
140 ui_resource_provider_ = ui_resource_provider;
143 void ThumbnailStore::AddThumbnailStoreObserver(
144 ThumbnailStoreObserver* observer) {
145 if (!observers_.HasObserver(observer))
146 observers_.AddObserver(observer);
149 void ThumbnailStore::RemoveThumbnailStoreObserver(
150 ThumbnailStoreObserver* observer) {
151 if (observers_.HasObserver(observer))
152 observers_.RemoveObserver(observer);
155 void ThumbnailStore::Put(TabId tab_id,
156 const SkBitmap& bitmap,
157 float thumbnail_scale) {
158 if (!ui_resource_provider_ || bitmap.empty() || thumbnail_scale <= 0)
161 DCHECK(thumbnail_meta_data_.find(tab_id) != thumbnail_meta_data_.end());
163 base::Time time_stamp = thumbnail_meta_data_[tab_id].capture_time();
164 scoped_ptr<Thumbnail> thumbnail = Thumbnail::Create(
165 tab_id, time_stamp, thumbnail_scale, ui_resource_provider_, this);
166 thumbnail->SetBitmap(bitmap);
168 RemoveFromReadQueue(tab_id);
169 MakeSpaceForNewItemIfNecessary(tab_id);
170 cache_.Put(tab_id, thumbnail.Pass());
172 if (use_approximation_thumbnail_) {
173 std::pair<SkBitmap, float> approximation =
174 CreateApproximation(bitmap, thumbnail_scale);
175 scoped_ptr<Thumbnail> approx_thumbnail = Thumbnail::Create(
176 tab_id, time_stamp, approximation.second, ui_resource_provider_, this);
177 approx_thumbnail->SetBitmap(approximation.first);
178 approximation_cache_.Put(tab_id, approx_thumbnail.Pass());
180 CompressThumbnailIfNecessary(tab_id, time_stamp, bitmap, thumbnail_scale);
183 void ThumbnailStore::Remove(TabId tab_id) {
184 cache_.Remove(tab_id);
185 approximation_cache_.Remove(tab_id);
186 thumbnail_meta_data_.erase(tab_id);
187 RemoveFromDisk(tab_id);
188 RemoveFromReadQueue(tab_id);
191 Thumbnail* ThumbnailStore::Get(TabId tab_id, bool force_disk_read) {
192 Thumbnail* thumbnail = cache_.Get(tab_id);
194 thumbnail->CreateUIResource();
198 if (force_disk_read &&
199 std::find(visible_ids_.begin(), visible_ids_.end(), tab_id) !=
200 visible_ids_.end() &&
201 std::find(read_queue_.begin(), read_queue_.end(), tab_id) ==
203 read_queue_.push_back(tab_id);
207 thumbnail = approximation_cache_.Get(tab_id);
209 thumbnail->CreateUIResource();
216 void ThumbnailStore::RemoveFromDiskAtAndAboveId(TabId min_id) {
217 base::Closure remove_task =
218 base::Bind(&ThumbnailStore::RemoveFromDiskAtAndAboveIdTask,
221 content::BrowserThread::PostTask(
222 content::BrowserThread::FILE, FROM_HERE, remove_task);
225 void ThumbnailStore::InvalidateThumbnailIfChanged(TabId tab_id,
227 ThumbnailMetaDataMap::iterator meta_data_iter =
228 thumbnail_meta_data_.find(tab_id);
229 if (meta_data_iter == thumbnail_meta_data_.end()) {
230 thumbnail_meta_data_[tab_id] = ThumbnailMetaData(base::Time(), url);
231 } else if (meta_data_iter->second.url() != url) {
236 bool ThumbnailStore::CheckAndUpdateThumbnailMetaData(TabId tab_id,
238 base::Time current_time = base::Time::Now();
239 ThumbnailMetaDataMap::iterator meta_data_iter =
240 thumbnail_meta_data_.find(tab_id);
241 if (meta_data_iter != thumbnail_meta_data_.end() &&
242 meta_data_iter->second.url() == url &&
243 (current_time - meta_data_iter->second.capture_time()) <
244 kCaptureMinRequestTimeMs) {
248 thumbnail_meta_data_[tab_id] = ThumbnailMetaData(current_time, url);
252 void ThumbnailStore::UpdateVisibleIds(const TabIdList& priority) {
253 if (priority.empty()) {
254 visible_ids_.clear();
258 size_t ids_size = std::min(priority.size(), cache_.MaximumCacheSize());
259 if (visible_ids_.size() == ids_size) {
260 // Early out if called with the same input as last time (We only care
261 // about the first mCache.MaximumCacheSize() entries).
262 bool lists_differ = false;
263 TabIdList::const_iterator visible_iter = visible_ids_.begin();
264 TabIdList::const_iterator priority_iter = priority.begin();
265 while (visible_iter != visible_ids_.end() &&
266 priority_iter != priority.end()) {
267 if (*priority_iter != *visible_iter) {
280 visible_ids_.clear();
282 TabIdList::const_iterator iter = priority.begin();
283 while (iter != priority.end() && count < ids_size) {
284 TabId tab_id = *iter;
285 visible_ids_.push_back(tab_id);
286 if (!cache_.Get(tab_id) &&
287 std::find(read_queue_.begin(), read_queue_.end(), tab_id) ==
289 read_queue_.push_back(tab_id);
298 void ThumbnailStore::RemoveFromDisk(TabId tab_id) {
299 base::FilePath file_path = GetFilePath(tab_id);
301 base::Bind(&ThumbnailStore::RemoveFromDiskTask, file_path);
302 content::BrowserThread::PostTask(
303 content::BrowserThread::FILE, FROM_HERE, task);
306 void ThumbnailStore::RemoveFromDiskTask(const base::FilePath& file_path) {
307 if (base::PathExists(file_path))
308 base::DeleteFile(file_path, false);
311 void ThumbnailStore::RemoveFromDiskAtAndAboveIdTask(
312 const base::FilePath& dir_path,
314 base::FileEnumerator enumerator(dir_path, false, base::FileEnumerator::FILES);
316 base::FilePath path = enumerator.Next();
319 base::FileEnumerator::FileInfo info = enumerator.GetInfo();
321 bool success = base::StringToInt(info.GetName().value(), &tab_id);
322 if (success && tab_id >= min_id)
323 base::DeleteFile(path, false);
327 void ThumbnailStore::WriteThumbnailIfNecessary(
329 skia::RefPtr<SkPixelRef> compressed_data,
331 const gfx::Size& content_size) {
332 if (write_tasks_count_ >= write_queue_max_size_)
335 write_tasks_count_++;
337 base::Callback<void()> post_write_task =
338 base::Bind(&ThumbnailStore::PostWriteTask, weak_factory_.GetWeakPtr());
339 content::BrowserThread::PostTask(content::BrowserThread::FILE,
341 base::Bind(&ThumbnailStore::WriteTask,
349 void ThumbnailStore::CompressThumbnailIfNecessary(
351 const base::Time& time_stamp,
352 const SkBitmap& bitmap,
354 if (compression_tasks_count_ >= compression_queue_max_size_) {
355 RemoveOnMatchedTimeStamp(tab_id, time_stamp);
359 compression_tasks_count_++;
361 base::Callback<void(skia::RefPtr<SkPixelRef>, const gfx::Size&)>
362 post_compression_task = base::Bind(&ThumbnailStore::PostCompressionTask,
363 weak_factory_.GetWeakPtr(),
368 base::WorkerPool::PostTask(
371 &ThumbnailStore::CompressionTask, bitmap, post_compression_task),
375 void ThumbnailStore::ReadNextThumbnail() {
376 if (read_queue_.empty() || read_in_progress_)
379 TabId tab_id = read_queue_.front();
380 read_in_progress_ = true;
382 base::FilePath file_path = GetFilePath(tab_id);
384 base::Callback<void(skia::RefPtr<SkPixelRef>, float, const gfx::Size&)>
385 post_read_task = base::Bind(
386 &ThumbnailStore::PostReadTask, weak_factory_.GetWeakPtr(), tab_id);
388 content::BrowserThread::PostTask(
389 content::BrowserThread::FILE,
391 base::Bind(&ThumbnailStore::ReadTask, file_path, post_read_task));
394 void ThumbnailStore::MakeSpaceForNewItemIfNecessary(TabId tab_id) {
395 if (cache_.Get(tab_id) ||
396 std::find(visible_ids_.begin(), visible_ids_.end(), tab_id) ==
397 visible_ids_.end() ||
398 cache_.size() < cache_.MaximumCacheSize()) {
403 bool found_key_to_remove = false;
405 // 1. Find a cached item not in this list
406 for (ExpiringThumbnailCache::iterator iter = cache_.begin();
407 iter != cache_.end();
409 if (std::find(visible_ids_.begin(), visible_ids_.end(), iter->first) ==
410 visible_ids_.end()) {
411 key_to_remove = iter->first;
412 found_key_to_remove = true;
417 if (!found_key_to_remove) {
418 // 2. Find the least important id we can remove.
419 for (TabIdList::reverse_iterator riter = visible_ids_.rbegin();
420 riter != visible_ids_.rend();
422 if (cache_.Get(*riter)) {
423 key_to_remove = *riter;
425 found_key_to_remove = true;
430 if (found_key_to_remove)
431 cache_.Remove(key_to_remove);
434 void ThumbnailStore::RemoveFromReadQueue(TabId tab_id) {
435 TabIdList::iterator read_iter =
436 std::find(read_queue_.begin(), read_queue_.end(), tab_id);
437 if (read_iter != read_queue_.end())
438 read_queue_.erase(read_iter);
441 void ThumbnailStore::InvalidateCachedThumbnail(Thumbnail* thumbnail) {
443 TabId tab_id = thumbnail->tab_id();
444 cc::UIResourceId uid = thumbnail->ui_resource_id();
446 Thumbnail* cached_thumbnail = cache_.Get(tab_id);
447 if (cached_thumbnail && cached_thumbnail->ui_resource_id() == uid)
448 cache_.Remove(tab_id);
450 cached_thumbnail = approximation_cache_.Get(tab_id);
451 if (cached_thumbnail && cached_thumbnail->ui_resource_id() == uid)
452 approximation_cache_.Remove(tab_id);
455 base::FilePath ThumbnailStore::GetFilePath(TabId tab_id) const {
456 return disk_cache_path_.Append(base::IntToString(tab_id));
461 bool WriteToFile(base::File& file,
462 const gfx::Size& content_size,
464 skia::RefPtr<SkPixelRef> compressed_data) {
468 if (!WriteBigEndianToFile(file, kCompressedKey))
471 if (!WriteBigEndianToFile(file, content_size.width()))
474 if (!WriteBigEndianToFile(file, content_size.height()))
477 // Write ETC1 header.
478 compressed_data->lockPixels();
480 unsigned char etc1_buffer[ETC_PKM_HEADER_SIZE];
481 etc1_pkm_format_header(etc1_buffer,
482 compressed_data->info().width(),
483 compressed_data->info().height());
485 int header_bytes_written = file.WriteAtCurrentPos(
486 reinterpret_cast<char*>(etc1_buffer), ETC_PKM_HEADER_SIZE);
487 if (header_bytes_written != ETC_PKM_HEADER_SIZE)
490 int data_size = etc1_get_encoded_data_size(
491 compressed_data->info().width(),
492 compressed_data->info().height());
493 int pixel_bytes_written = file.WriteAtCurrentPos(
494 reinterpret_cast<char*>(compressed_data->pixels()),
496 if (pixel_bytes_written != data_size)
499 compressed_data->unlockPixels();
501 if (!WriteBigEndianToFile(file, kCurrentExtraVersion))
504 if (!WriteBigEndianFloatToFile(file, 1.f / scale))
510 } // anonymous namespace
512 void ThumbnailStore::WriteTask(const base::FilePath& file_path,
513 skia::RefPtr<SkPixelRef> compressed_data,
515 const gfx::Size& content_size,
516 const base::Callback<void()>& post_write_task) {
517 DCHECK(compressed_data);
519 base::File file(file_path,
520 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
522 bool success = WriteToFile(file,
530 base::DeleteFile(file_path, false);
532 content::BrowserThread::PostTask(
533 content::BrowserThread::UI, FROM_HERE, post_write_task);
536 void ThumbnailStore::PostWriteTask() {
537 write_tasks_count_--;
540 void ThumbnailStore::CompressionTask(
542 const base::Callback<void(skia::RefPtr<SkPixelRef>, const gfx::Size&)>&
543 post_compression_task) {
544 skia::RefPtr<SkPixelRef> compressed_data;
545 gfx::Size content_size;
547 if (!raw_data.empty()) {
548 SkAutoLockPixels raw_data_lock(raw_data);
549 gfx::Size raw_data_size(raw_data.width(), raw_data.height());
550 size_t pixel_size = 4; // Pixel size is 4 bytes for kARGB_8888_Config.
551 size_t stride = pixel_size * raw_data_size.width();
553 gfx::Size encoded_size = GetEncodedSize(raw_data_size);
554 size_t encoded_bytes =
555 etc1_get_encoded_data_size(encoded_size.width(), encoded_size.height());
556 SkImageInfo info = {encoded_size.width(),
557 encoded_size.height(),
558 kUnknown_SkColorType,
559 kUnpremul_SkAlphaType};
560 skia::RefPtr<SkData> etc1_pixel_data = skia::AdoptRef(
561 SkData::NewFromMalloc(new uint8_t[encoded_bytes], encoded_bytes));
562 skia::RefPtr<SkMallocPixelRef> etc1_pixel_ref = skia::AdoptRef(
563 SkMallocPixelRef::NewWithData(info, 0, NULL, etc1_pixel_data.get()));
565 etc1_pixel_ref->lockPixels();
566 bool success = etc1_encode_image(
567 reinterpret_cast<unsigned char*>(raw_data.getPixels()),
568 raw_data_size.width(),
569 raw_data_size.height(),
572 reinterpret_cast<unsigned char*>(etc1_pixel_ref->pixels()),
573 encoded_size.width(),
574 encoded_size.height());
575 etc1_pixel_ref->setImmutable();
576 etc1_pixel_ref->unlockPixels();
579 compressed_data = etc1_pixel_ref;
580 content_size = raw_data_size;
584 content::BrowserThread::PostTask(
585 content::BrowserThread::UI,
587 base::Bind(post_compression_task, compressed_data, content_size));
590 void ThumbnailStore::PostCompressionTask(
592 const base::Time& time_stamp,
594 skia::RefPtr<SkPixelRef> compressed_data,
595 const gfx::Size& content_size) {
596 compression_tasks_count_--;
597 if (!compressed_data) {
598 RemoveOnMatchedTimeStamp(tab_id, time_stamp);
602 Thumbnail* thumbnail = cache_.Get(tab_id);
604 if (thumbnail->time_stamp() != time_stamp)
606 thumbnail->SetCompressedBitmap(compressed_data, content_size);
607 thumbnail->CreateUIResource();
609 WriteThumbnailIfNecessary(tab_id, compressed_data, scale, content_size);
614 bool ReadFromFile(base::File& file,
615 gfx::Size* out_content_size,
617 skia::RefPtr<SkPixelRef>* out_pixels) {
622 if (!ReadBigEndianFromFile(file, &key))
625 if (key != kCompressedKey)
628 int content_width = 0;
629 if (!ReadBigEndianFromFile(file, &content_width) || content_width <= 0)
632 int content_height = 0;
633 if (!ReadBigEndianFromFile(file, &content_height) || content_height <= 0)
636 out_content_size->SetSize(content_width, content_height);
639 int header_bytes_read = 0;
640 unsigned char etc1_buffer[ETC_PKM_HEADER_SIZE];
641 header_bytes_read = file.ReadAtCurrentPos(
642 reinterpret_cast<char*>(etc1_buffer),
643 ETC_PKM_HEADER_SIZE);
644 if (header_bytes_read != ETC_PKM_HEADER_SIZE)
647 if (!etc1_pkm_is_valid(etc1_buffer))
651 raw_width = etc1_pkm_get_width(etc1_buffer);
656 raw_height = etc1_pkm_get_height(etc1_buffer);
660 // Do some simple sanity check validation. We can't have thumbnails larger
661 // than the max display size of the screen. We also can't have etc1 texture
662 // data larger than the next power of 2 up from that.
663 gfx::DeviceDisplayInfo display_info;
664 int max_dimension = std::max(display_info.GetDisplayWidth(),
665 display_info.GetDisplayHeight());
667 if (content_width > max_dimension
668 || content_height > max_dimension
669 || static_cast<size_t>(raw_width) > NextPowerOfTwo(max_dimension)
670 || static_cast<size_t>(raw_height) > NextPowerOfTwo(max_dimension)) {
674 skia::RefPtr<SkData> etc1_pixel_data;
675 int data_size = etc1_get_encoded_data_size(raw_width, raw_height);
676 scoped_ptr<uint8_t[]> raw_data =
677 scoped_ptr<uint8_t[]>(new uint8_t[data_size]);
679 int pixel_bytes_read = file.ReadAtCurrentPos(
680 reinterpret_cast<char*>(raw_data.get()),
683 if (pixel_bytes_read != data_size)
686 SkImageInfo info = {raw_width,
688 kUnknown_SkColorType,
689 kUnpremul_SkAlphaType};
691 etc1_pixel_data = skia::AdoptRef(
692 SkData::NewFromMalloc(raw_data.release(), data_size));
694 *out_pixels = skia::AdoptRef(
695 SkMallocPixelRef::NewWithData(info,
698 etc1_pixel_data.get()));
700 int extra_data_version = 0;
701 if (!ReadBigEndianFromFile(file, &extra_data_version))
705 if (extra_data_version == 1) {
706 if (!ReadBigEndianFloatFromFile(file, out_scale))
709 if (*out_scale == 0.f)
712 *out_scale = 1.f / *out_scale;
718 }// anonymous namespace
720 void ThumbnailStore::ReadTask(
721 const base::FilePath& file_path,
722 const base::Callback<
723 void(skia::RefPtr<SkPixelRef>, float, const gfx::Size&)>&
725 gfx::Size content_size;
727 skia::RefPtr<SkPixelRef> compressed_data;
729 if (base::PathExists(file_path)) {
730 base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
732 bool valid_contents = ReadFromFile(file,
738 if (!valid_contents) {
739 content_size.SetSize(0, 0);
741 compressed_data.clear();
742 base::DeleteFile(file_path, false);
746 content::BrowserThread::PostTask(
747 content::BrowserThread::UI,
749 base::Bind(post_read_task, compressed_data, scale, content_size));
752 void ThumbnailStore::PostReadTask(TabId tab_id,
753 skia::RefPtr<SkPixelRef> compressed_data,
755 const gfx::Size& content_size) {
756 read_in_progress_ = false;
758 TabIdList::iterator iter =
759 std::find(read_queue_.begin(), read_queue_.end(), tab_id);
760 if (iter == read_queue_.end()) {
765 read_queue_.erase(iter);
767 if (!cache_.Get(tab_id) && compressed_data) {
768 ThumbnailMetaDataMap::iterator meta_iter =
769 thumbnail_meta_data_.find(tab_id);
770 base::Time time_stamp = base::Time::Now();
771 if (meta_iter != thumbnail_meta_data_.end())
772 time_stamp = meta_iter->second.capture_time();
774 MakeSpaceForNewItemIfNecessary(tab_id);
775 scoped_ptr<Thumbnail> thumbnail = Thumbnail::Create(
776 tab_id, time_stamp, scale, ui_resource_provider_, this);
777 thumbnail->SetCompressedBitmap(compressed_data,
779 if (kPreferCPUMemory)
780 thumbnail->CreateUIResource();
782 cache_.Put(tab_id, thumbnail.Pass());
783 NotifyObserversOfThumbnailRead(tab_id);
789 void ThumbnailStore::NotifyObserversOfThumbnailRead(TabId tab_id) {
791 ThumbnailStoreObserver, observers_, OnFinishedThumbnailRead(tab_id));
794 void ThumbnailStore::RemoveOnMatchedTimeStamp(TabId tab_id,
795 const base::Time& time_stamp) {
796 // We remove the cached version if it matches the tab_id and the time_stamp.
797 Thumbnail* thumbnail = cache_.Get(tab_id);
798 Thumbnail* approx_thumbnail = approximation_cache_.Get(tab_id);
799 if ((thumbnail && thumbnail->time_stamp() == time_stamp) ||
800 (approx_thumbnail && approx_thumbnail->time_stamp() == time_stamp)) {
806 ThumbnailStore::ThumbnailMetaData::ThumbnailMetaData() {
809 ThumbnailStore::ThumbnailMetaData::ThumbnailMetaData(
810 const base::Time& current_time,
812 : capture_time_(current_time), url_(url) {
815 std::pair<SkBitmap, float> ThumbnailStore::CreateApproximation(
816 const SkBitmap& bitmap,
818 DCHECK(!bitmap.empty());
820 SkAutoLockPixels bitmap_lock(bitmap);
821 float new_scale = 1.f / kApproximationScaleFactor;
823 gfx::Size dst_size = gfx::ToFlooredSize(
824 gfx::ScaleSize(gfx::Size(bitmap.width(), bitmap.height()), new_scale));
826 dst_bitmap.allocPixels(SkImageInfo::Make(dst_size.width(),
828 bitmap.info().fColorType,
829 bitmap.info().fAlphaType));
830 dst_bitmap.eraseColor(0);
831 SkAutoLockPixels dst_bitmap_lock(dst_bitmap);
833 SkCanvas canvas(dst_bitmap);
834 canvas.scale(new_scale, new_scale);
835 canvas.drawBitmap(bitmap, 0, 0, NULL);
836 dst_bitmap.setImmutable();
838 return std::make_pair(dst_bitmap, new_scale * scale);