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/files/file.h"
12 #include "base/files/file_enumerator.h"
13 #include "base/files/file_path.h"
14 #include "base/files/file_util.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 size_t NextPowerOfTwo(size_t x) {
51 size_t RoundUpMod4(size_t x) {
55 gfx::Size GetEncodedSize(const gfx::Size& bitmap_size, bool supports_npot) {
56 DCHECK(!bitmap_size.IsEmpty());
58 return gfx::Size(NextPowerOfTwo(bitmap_size.width()),
59 NextPowerOfTwo(bitmap_size.height()));
61 return gfx::Size(RoundUpMod4(bitmap_size.width()),
62 RoundUpMod4(bitmap_size.height()));
66 bool ReadBigEndianFromFile(base::File& file, T* out) {
67 char buffer[sizeof(T)];
68 if (file.ReadAtCurrentPos(buffer, sizeof(T)) != sizeof(T))
70 base::ReadBigEndian(buffer, out);
75 bool WriteBigEndianToFile(base::File& file, T val) {
76 char buffer[sizeof(T)];
77 base::WriteBigEndian(buffer, val);
78 return file.WriteAtCurrentPos(buffer, sizeof(T)) == sizeof(T);
81 bool ReadBigEndianFloatFromFile(base::File& file, float* out) {
82 char buffer[sizeof(float)];
83 if (file.ReadAtCurrentPos(buffer, sizeof(buffer)) != sizeof(buffer))
86 #if defined(ARCH_CPU_LITTLE_ENDIAN)
87 for (size_t i = 0; i < sizeof(float) / 2; i++) {
89 buffer[i] = buffer[sizeof(float) - 1 - i];
90 buffer[sizeof(float) - 1 - i] = tmp;
93 memcpy(out, buffer, sizeof(buffer));
98 bool WriteBigEndianFloatToFile(base::File& file, float val) {
99 char buffer[sizeof(float)];
100 memcpy(buffer, &val, sizeof(buffer));
102 #if defined(ARCH_CPU_LITTLE_ENDIAN)
103 for (size_t i = 0; i < sizeof(float) / 2; i++) {
104 char tmp = buffer[i];
105 buffer[i] = buffer[sizeof(float) - 1 - i];
106 buffer[sizeof(float) - 1 - i] = tmp;
109 return file.WriteAtCurrentPos(buffer, sizeof(buffer)) == sizeof(buffer);
112 } // anonymous namespace
114 ThumbnailStore::ThumbnailStore(const std::string& disk_cache_path_str,
115 size_t default_cache_size,
116 size_t approximation_cache_size,
117 size_t compression_queue_max_size,
118 size_t write_queue_max_size,
119 bool use_approximation_thumbnail)
120 : disk_cache_path_(disk_cache_path_str),
121 compression_queue_max_size_(compression_queue_max_size),
122 write_queue_max_size_(write_queue_max_size),
123 use_approximation_thumbnail_(use_approximation_thumbnail),
124 compression_tasks_count_(0),
125 write_tasks_count_(0),
126 read_in_progress_(false),
127 cache_(default_cache_size),
128 approximation_cache_(approximation_cache_size),
129 ui_resource_provider_(NULL),
130 weak_factory_(this) {
131 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
134 ThumbnailStore::~ThumbnailStore() {
135 SetUIResourceProvider(NULL);
138 void ThumbnailStore::SetUIResourceProvider(
139 content::UIResourceProvider* ui_resource_provider) {
140 if (ui_resource_provider_ == ui_resource_provider)
143 approximation_cache_.Clear();
146 ui_resource_provider_ = ui_resource_provider;
149 void ThumbnailStore::AddThumbnailStoreObserver(
150 ThumbnailStoreObserver* observer) {
151 if (!observers_.HasObserver(observer))
152 observers_.AddObserver(observer);
155 void ThumbnailStore::RemoveThumbnailStoreObserver(
156 ThumbnailStoreObserver* observer) {
157 if (observers_.HasObserver(observer))
158 observers_.RemoveObserver(observer);
161 void ThumbnailStore::Put(TabId tab_id,
162 const SkBitmap& bitmap,
163 float thumbnail_scale) {
164 if (!ui_resource_provider_ || bitmap.empty() || thumbnail_scale <= 0)
167 DCHECK(thumbnail_meta_data_.find(tab_id) != thumbnail_meta_data_.end());
169 base::Time time_stamp = thumbnail_meta_data_[tab_id].capture_time();
170 scoped_ptr<Thumbnail> thumbnail = Thumbnail::Create(
171 tab_id, time_stamp, thumbnail_scale, ui_resource_provider_, this);
172 thumbnail->SetBitmap(bitmap);
174 RemoveFromReadQueue(tab_id);
175 MakeSpaceForNewItemIfNecessary(tab_id);
176 cache_.Put(tab_id, thumbnail.Pass());
178 if (use_approximation_thumbnail_) {
179 std::pair<SkBitmap, float> approximation =
180 CreateApproximation(bitmap, thumbnail_scale);
181 scoped_ptr<Thumbnail> approx_thumbnail = Thumbnail::Create(
182 tab_id, time_stamp, approximation.second, ui_resource_provider_, this);
183 approx_thumbnail->SetBitmap(approximation.first);
184 approximation_cache_.Put(tab_id, approx_thumbnail.Pass());
186 CompressThumbnailIfNecessary(tab_id, time_stamp, bitmap, thumbnail_scale);
189 void ThumbnailStore::Remove(TabId tab_id) {
190 cache_.Remove(tab_id);
191 approximation_cache_.Remove(tab_id);
192 thumbnail_meta_data_.erase(tab_id);
193 RemoveFromDisk(tab_id);
194 RemoveFromReadQueue(tab_id);
197 Thumbnail* ThumbnailStore::Get(TabId tab_id, bool force_disk_read) {
198 Thumbnail* thumbnail = cache_.Get(tab_id);
200 thumbnail->CreateUIResource();
204 if (force_disk_read &&
205 std::find(visible_ids_.begin(), visible_ids_.end(), tab_id) !=
206 visible_ids_.end() &&
207 std::find(read_queue_.begin(), read_queue_.end(), tab_id) ==
209 read_queue_.push_back(tab_id);
213 thumbnail = approximation_cache_.Get(tab_id);
215 thumbnail->CreateUIResource();
222 void ThumbnailStore::RemoveFromDiskAtAndAboveId(TabId min_id) {
223 base::Closure remove_task =
224 base::Bind(&ThumbnailStore::RemoveFromDiskAtAndAboveIdTask,
227 content::BrowserThread::PostTask(
228 content::BrowserThread::FILE, FROM_HERE, remove_task);
231 void ThumbnailStore::InvalidateThumbnailIfChanged(TabId tab_id,
233 ThumbnailMetaDataMap::iterator meta_data_iter =
234 thumbnail_meta_data_.find(tab_id);
235 if (meta_data_iter == thumbnail_meta_data_.end()) {
236 thumbnail_meta_data_[tab_id] = ThumbnailMetaData(base::Time(), url);
237 } else if (meta_data_iter->second.url() != url) {
242 bool ThumbnailStore::CheckAndUpdateThumbnailMetaData(TabId tab_id,
244 base::Time current_time = base::Time::Now();
245 ThumbnailMetaDataMap::iterator meta_data_iter =
246 thumbnail_meta_data_.find(tab_id);
247 if (meta_data_iter != thumbnail_meta_data_.end() &&
248 meta_data_iter->second.url() == url &&
249 (current_time - meta_data_iter->second.capture_time()) <
250 kCaptureMinRequestTimeMs) {
254 thumbnail_meta_data_[tab_id] = ThumbnailMetaData(current_time, url);
258 void ThumbnailStore::UpdateVisibleIds(const TabIdList& priority) {
259 if (priority.empty()) {
260 visible_ids_.clear();
264 size_t ids_size = std::min(priority.size(), cache_.MaximumCacheSize());
265 if (visible_ids_.size() == ids_size) {
266 // Early out if called with the same input as last time (We only care
267 // about the first mCache.MaximumCacheSize() entries).
268 bool lists_differ = false;
269 TabIdList::const_iterator visible_iter = visible_ids_.begin();
270 TabIdList::const_iterator priority_iter = priority.begin();
271 while (visible_iter != visible_ids_.end() &&
272 priority_iter != priority.end()) {
273 if (*priority_iter != *visible_iter) {
286 visible_ids_.clear();
288 TabIdList::const_iterator iter = priority.begin();
289 while (iter != priority.end() && count < ids_size) {
290 TabId tab_id = *iter;
291 visible_ids_.push_back(tab_id);
292 if (!cache_.Get(tab_id) &&
293 std::find(read_queue_.begin(), read_queue_.end(), tab_id) ==
295 read_queue_.push_back(tab_id);
304 void ThumbnailStore::DecompressThumbnailFromFile(
306 const base::Callback<void(bool, SkBitmap)>&
307 post_decompress_callback) {
308 base::FilePath file_path = GetFilePath(tab_id);
310 base::Callback<void(skia::RefPtr<SkPixelRef>, float, const gfx::Size&)>
311 decompress_task = base::Bind(
312 &ThumbnailStore::DecompressionTask, post_decompress_callback);
314 content::BrowserThread::PostTask(
315 content::BrowserThread::FILE,
317 base::Bind(&ThumbnailStore::ReadTask, true, file_path, decompress_task));
320 void ThumbnailStore::RemoveFromDisk(TabId tab_id) {
321 base::FilePath file_path = GetFilePath(tab_id);
323 base::Bind(&ThumbnailStore::RemoveFromDiskTask, file_path);
324 content::BrowserThread::PostTask(
325 content::BrowserThread::FILE, FROM_HERE, task);
328 void ThumbnailStore::RemoveFromDiskTask(const base::FilePath& file_path) {
329 if (base::PathExists(file_path))
330 base::DeleteFile(file_path, false);
333 void ThumbnailStore::RemoveFromDiskAtAndAboveIdTask(
334 const base::FilePath& dir_path,
336 base::FileEnumerator enumerator(dir_path, false, base::FileEnumerator::FILES);
338 base::FilePath path = enumerator.Next();
341 base::FileEnumerator::FileInfo info = enumerator.GetInfo();
343 bool success = base::StringToInt(info.GetName().value(), &tab_id);
344 if (success && tab_id >= min_id)
345 base::DeleteFile(path, false);
349 void ThumbnailStore::WriteThumbnailIfNecessary(
351 skia::RefPtr<SkPixelRef> compressed_data,
353 const gfx::Size& content_size) {
354 if (write_tasks_count_ >= write_queue_max_size_)
357 write_tasks_count_++;
359 base::Callback<void()> post_write_task =
360 base::Bind(&ThumbnailStore::PostWriteTask, weak_factory_.GetWeakPtr());
361 content::BrowserThread::PostTask(content::BrowserThread::FILE,
363 base::Bind(&ThumbnailStore::WriteTask,
371 void ThumbnailStore::CompressThumbnailIfNecessary(
373 const base::Time& time_stamp,
374 const SkBitmap& bitmap,
376 if (compression_tasks_count_ >= compression_queue_max_size_) {
377 RemoveOnMatchedTimeStamp(tab_id, time_stamp);
381 compression_tasks_count_++;
383 base::Callback<void(skia::RefPtr<SkPixelRef>, const gfx::Size&)>
384 post_compression_task = base::Bind(&ThumbnailStore::PostCompressionTask,
385 weak_factory_.GetWeakPtr(),
390 gfx::Size raw_data_size(bitmap.width(), bitmap.height());
391 gfx::Size encoded_size = GetEncodedSize(
392 raw_data_size, ui_resource_provider_->SupportsETC1NonPowerOfTwo());
394 base::WorkerPool::PostTask(FROM_HERE,
395 base::Bind(&ThumbnailStore::CompressionTask,
398 post_compression_task),
402 void ThumbnailStore::ReadNextThumbnail() {
403 if (read_queue_.empty() || read_in_progress_)
406 TabId tab_id = read_queue_.front();
407 read_in_progress_ = true;
409 base::FilePath file_path = GetFilePath(tab_id);
411 base::Callback<void(skia::RefPtr<SkPixelRef>, float, const gfx::Size&)>
412 post_read_task = base::Bind(
413 &ThumbnailStore::PostReadTask, weak_factory_.GetWeakPtr(), tab_id);
415 content::BrowserThread::PostTask(
416 content::BrowserThread::FILE,
418 base::Bind(&ThumbnailStore::ReadTask, false, file_path, post_read_task));
421 void ThumbnailStore::MakeSpaceForNewItemIfNecessary(TabId tab_id) {
422 if (cache_.Get(tab_id) ||
423 std::find(visible_ids_.begin(), visible_ids_.end(), tab_id) ==
424 visible_ids_.end() ||
425 cache_.size() < cache_.MaximumCacheSize()) {
430 bool found_key_to_remove = false;
432 // 1. Find a cached item not in this list
433 for (ExpiringThumbnailCache::iterator iter = cache_.begin();
434 iter != cache_.end();
436 if (std::find(visible_ids_.begin(), visible_ids_.end(), iter->first) ==
437 visible_ids_.end()) {
438 key_to_remove = iter->first;
439 found_key_to_remove = true;
444 if (!found_key_to_remove) {
445 // 2. Find the least important id we can remove.
446 for (TabIdList::reverse_iterator riter = visible_ids_.rbegin();
447 riter != visible_ids_.rend();
449 if (cache_.Get(*riter)) {
450 key_to_remove = *riter;
452 found_key_to_remove = true;
457 if (found_key_to_remove)
458 cache_.Remove(key_to_remove);
461 void ThumbnailStore::RemoveFromReadQueue(TabId tab_id) {
462 TabIdList::iterator read_iter =
463 std::find(read_queue_.begin(), read_queue_.end(), tab_id);
464 if (read_iter != read_queue_.end())
465 read_queue_.erase(read_iter);
468 void ThumbnailStore::InvalidateCachedThumbnail(Thumbnail* thumbnail) {
470 TabId tab_id = thumbnail->tab_id();
471 cc::UIResourceId uid = thumbnail->ui_resource_id();
473 Thumbnail* cached_thumbnail = cache_.Get(tab_id);
474 if (cached_thumbnail && cached_thumbnail->ui_resource_id() == uid)
475 cache_.Remove(tab_id);
477 cached_thumbnail = approximation_cache_.Get(tab_id);
478 if (cached_thumbnail && cached_thumbnail->ui_resource_id() == uid)
479 approximation_cache_.Remove(tab_id);
482 base::FilePath ThumbnailStore::GetFilePath(TabId tab_id) const {
483 return disk_cache_path_.Append(base::IntToString(tab_id));
488 bool WriteToFile(base::File& file,
489 const gfx::Size& content_size,
491 skia::RefPtr<SkPixelRef> compressed_data) {
495 if (!WriteBigEndianToFile(file, kCompressedKey))
498 if (!WriteBigEndianToFile(file, content_size.width()))
501 if (!WriteBigEndianToFile(file, content_size.height()))
504 // Write ETC1 header.
505 compressed_data->lockPixels();
507 unsigned char etc1_buffer[ETC_PKM_HEADER_SIZE];
508 etc1_pkm_format_header(etc1_buffer,
509 compressed_data->info().width(),
510 compressed_data->info().height());
512 int header_bytes_written = file.WriteAtCurrentPos(
513 reinterpret_cast<char*>(etc1_buffer), ETC_PKM_HEADER_SIZE);
514 if (header_bytes_written != ETC_PKM_HEADER_SIZE)
517 int data_size = etc1_get_encoded_data_size(
518 compressed_data->info().width(),
519 compressed_data->info().height());
520 int pixel_bytes_written = file.WriteAtCurrentPos(
521 reinterpret_cast<char*>(compressed_data->pixels()),
523 if (pixel_bytes_written != data_size)
526 compressed_data->unlockPixels();
528 if (!WriteBigEndianToFile(file, kCurrentExtraVersion))
531 if (!WriteBigEndianFloatToFile(file, 1.f / scale))
537 } // anonymous namespace
539 void ThumbnailStore::WriteTask(const base::FilePath& file_path,
540 skia::RefPtr<SkPixelRef> compressed_data,
542 const gfx::Size& content_size,
543 const base::Callback<void()>& post_write_task) {
544 DCHECK(compressed_data);
546 base::File file(file_path,
547 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
549 bool success = WriteToFile(file,
557 base::DeleteFile(file_path, false);
559 content::BrowserThread::PostTask(
560 content::BrowserThread::UI, FROM_HERE, post_write_task);
563 void ThumbnailStore::PostWriteTask() {
564 write_tasks_count_--;
567 void ThumbnailStore::CompressionTask(
569 gfx::Size encoded_size,
570 const base::Callback<void(skia::RefPtr<SkPixelRef>, const gfx::Size&)>&
571 post_compression_task) {
572 skia::RefPtr<SkPixelRef> compressed_data;
573 gfx::Size content_size;
575 if (!raw_data.empty()) {
576 SkAutoLockPixels raw_data_lock(raw_data);
577 gfx::Size raw_data_size(raw_data.width(), raw_data.height());
578 size_t pixel_size = 4; // Pixel size is 4 bytes for kARGB_8888_Config.
579 size_t stride = pixel_size * raw_data_size.width();
581 size_t encoded_bytes =
582 etc1_get_encoded_data_size(encoded_size.width(), encoded_size.height());
583 SkImageInfo info = SkImageInfo::Make(encoded_size.width(),
584 encoded_size.height(),
585 kUnknown_SkColorType,
586 kUnpremul_SkAlphaType);
587 skia::RefPtr<SkData> etc1_pixel_data = skia::AdoptRef(
588 SkData::NewUninitialized(encoded_bytes));
589 skia::RefPtr<SkMallocPixelRef> etc1_pixel_ref = skia::AdoptRef(
590 SkMallocPixelRef::NewWithData(info, 0, NULL, etc1_pixel_data.get()));
592 etc1_pixel_ref->lockPixels();
593 bool success = etc1_encode_image(
594 reinterpret_cast<unsigned char*>(raw_data.getPixels()),
595 raw_data_size.width(),
596 raw_data_size.height(),
599 reinterpret_cast<unsigned char*>(etc1_pixel_ref->pixels()),
600 encoded_size.width(),
601 encoded_size.height());
602 etc1_pixel_ref->setImmutable();
603 etc1_pixel_ref->unlockPixels();
606 compressed_data = etc1_pixel_ref;
607 content_size = raw_data_size;
611 content::BrowserThread::PostTask(
612 content::BrowserThread::UI,
614 base::Bind(post_compression_task, compressed_data, content_size));
617 void ThumbnailStore::PostCompressionTask(
619 const base::Time& time_stamp,
621 skia::RefPtr<SkPixelRef> compressed_data,
622 const gfx::Size& content_size) {
623 compression_tasks_count_--;
624 if (!compressed_data) {
625 RemoveOnMatchedTimeStamp(tab_id, time_stamp);
629 Thumbnail* thumbnail = cache_.Get(tab_id);
631 if (thumbnail->time_stamp() != time_stamp)
633 thumbnail->SetCompressedBitmap(compressed_data, content_size);
634 thumbnail->CreateUIResource();
636 WriteThumbnailIfNecessary(tab_id, compressed_data, scale, content_size);
641 bool ReadFromFile(base::File& file,
642 gfx::Size* out_content_size,
644 skia::RefPtr<SkPixelRef>* out_pixels) {
649 if (!ReadBigEndianFromFile(file, &key))
652 if (key != kCompressedKey)
655 int content_width = 0;
656 if (!ReadBigEndianFromFile(file, &content_width) || content_width <= 0)
659 int content_height = 0;
660 if (!ReadBigEndianFromFile(file, &content_height) || content_height <= 0)
663 out_content_size->SetSize(content_width, content_height);
666 int header_bytes_read = 0;
667 unsigned char etc1_buffer[ETC_PKM_HEADER_SIZE];
668 header_bytes_read = file.ReadAtCurrentPos(
669 reinterpret_cast<char*>(etc1_buffer),
670 ETC_PKM_HEADER_SIZE);
671 if (header_bytes_read != ETC_PKM_HEADER_SIZE)
674 if (!etc1_pkm_is_valid(etc1_buffer))
678 raw_width = etc1_pkm_get_width(etc1_buffer);
683 raw_height = etc1_pkm_get_height(etc1_buffer);
687 // Do some simple sanity check validation. We can't have thumbnails larger
688 // than the max display size of the screen. We also can't have etc1 texture
689 // data larger than the next power of 2 up from that.
690 gfx::DeviceDisplayInfo display_info;
691 int max_dimension = std::max(display_info.GetDisplayWidth(),
692 display_info.GetDisplayHeight());
694 if (content_width > max_dimension
695 || content_height > max_dimension
696 || static_cast<size_t>(raw_width) > NextPowerOfTwo(max_dimension)
697 || static_cast<size_t>(raw_height) > NextPowerOfTwo(max_dimension)) {
701 int data_size = etc1_get_encoded_data_size(raw_width, raw_height);
702 skia::RefPtr<SkData> etc1_pixel_data =
703 skia::AdoptRef(SkData::NewUninitialized(data_size));
705 int pixel_bytes_read = file.ReadAtCurrentPos(
706 reinterpret_cast<char*>(etc1_pixel_data->writable_data()),
709 if (pixel_bytes_read != data_size)
712 SkImageInfo info = SkImageInfo::Make(raw_width,
714 kUnknown_SkColorType,
715 kUnpremul_SkAlphaType);
717 *out_pixels = skia::AdoptRef(
718 SkMallocPixelRef::NewWithData(info,
721 etc1_pixel_data.get()));
723 int extra_data_version = 0;
724 if (!ReadBigEndianFromFile(file, &extra_data_version))
728 if (extra_data_version == 1) {
729 if (!ReadBigEndianFloatFromFile(file, out_scale))
732 if (*out_scale == 0.f)
735 *out_scale = 1.f / *out_scale;
741 }// anonymous namespace
743 void ThumbnailStore::ReadTask(
745 const base::FilePath& file_path,
746 const base::Callback<
747 void(skia::RefPtr<SkPixelRef>, float, const gfx::Size&)>&
749 gfx::Size content_size;
751 skia::RefPtr<SkPixelRef> compressed_data;
753 if (base::PathExists(file_path)) {
754 base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
757 bool valid_contents = ReadFromFile(file,
763 if (!valid_contents) {
764 content_size.SetSize(0, 0);
766 compressed_data.clear();
767 base::DeleteFile(file_path, false);
772 base::WorkerPool::PostTask(
774 base::Bind(post_read_task, compressed_data, scale, content_size),
777 content::BrowserThread::PostTask(
778 content::BrowserThread::UI,
780 base::Bind(post_read_task, compressed_data, scale, content_size));
784 void ThumbnailStore::PostReadTask(TabId tab_id,
785 skia::RefPtr<SkPixelRef> compressed_data,
787 const gfx::Size& content_size) {
788 read_in_progress_ = false;
790 TabIdList::iterator iter =
791 std::find(read_queue_.begin(), read_queue_.end(), tab_id);
792 if (iter == read_queue_.end()) {
797 read_queue_.erase(iter);
799 if (!cache_.Get(tab_id) && compressed_data) {
800 ThumbnailMetaDataMap::iterator meta_iter =
801 thumbnail_meta_data_.find(tab_id);
802 base::Time time_stamp = base::Time::Now();
803 if (meta_iter != thumbnail_meta_data_.end())
804 time_stamp = meta_iter->second.capture_time();
806 MakeSpaceForNewItemIfNecessary(tab_id);
807 scoped_ptr<Thumbnail> thumbnail = Thumbnail::Create(
808 tab_id, time_stamp, scale, ui_resource_provider_, this);
809 thumbnail->SetCompressedBitmap(compressed_data,
811 if (kPreferCPUMemory)
812 thumbnail->CreateUIResource();
814 cache_.Put(tab_id, thumbnail.Pass());
815 NotifyObserversOfThumbnailRead(tab_id);
821 void ThumbnailStore::NotifyObserversOfThumbnailRead(TabId tab_id) {
823 ThumbnailStoreObserver, observers_, OnFinishedThumbnailRead(tab_id));
826 void ThumbnailStore::RemoveOnMatchedTimeStamp(TabId tab_id,
827 const base::Time& time_stamp) {
828 // We remove the cached version if it matches the tab_id and the time_stamp.
829 Thumbnail* thumbnail = cache_.Get(tab_id);
830 Thumbnail* approx_thumbnail = approximation_cache_.Get(tab_id);
831 if ((thumbnail && thumbnail->time_stamp() == time_stamp) ||
832 (approx_thumbnail && approx_thumbnail->time_stamp() == time_stamp)) {
838 void ThumbnailStore::DecompressionTask(
839 const base::Callback<void(bool, SkBitmap)>&
840 post_decompression_callback,
841 skia::RefPtr<SkPixelRef> compressed_data,
843 const gfx::Size& content_size) {
844 SkBitmap raw_data_small;
845 bool success = false;
847 if (compressed_data.get()) {
848 gfx::Size buffer_size = gfx::Size(compressed_data->info().width(),
849 compressed_data->info().height());
852 raw_data.allocPixels(SkImageInfo::Make(buffer_size.width(),
853 buffer_size.height(),
854 kRGBA_8888_SkColorType,
855 kOpaque_SkAlphaType));
856 SkAutoLockPixels raw_data_lock(raw_data);
857 compressed_data->lockPixels();
858 success = etc1_decode_image(
859 reinterpret_cast<unsigned char*>(compressed_data->pixels()),
860 reinterpret_cast<unsigned char*>(raw_data.getPixels()),
862 buffer_size.height(),
863 raw_data.bytesPerPixel(),
864 raw_data.rowBytes());
865 compressed_data->unlockPixels();
866 raw_data.setImmutable();
869 // Leave raw_data_small empty for consistency with other failure modes.
870 } else if (content_size == buffer_size) {
871 // Shallow copy the pixel reference.
872 raw_data_small = raw_data;
874 // The content size is smaller than the buffer size (likely because of
875 // a power-of-two rounding), so deep copy the bitmap.
876 raw_data_small.allocPixels(SkImageInfo::Make(content_size.width(),
877 content_size.height(),
878 kRGBA_8888_SkColorType,
879 kOpaque_SkAlphaType));
880 SkAutoLockPixels raw_data_small_lock(raw_data_small);
881 SkCanvas small_canvas(raw_data_small);
882 small_canvas.drawBitmap(raw_data, 0, 0);
883 raw_data_small.setImmutable();
887 content::BrowserThread::PostTask(
888 content::BrowserThread::UI,
890 base::Bind(post_decompression_callback, success, raw_data_small));
893 ThumbnailStore::ThumbnailMetaData::ThumbnailMetaData() {
896 ThumbnailStore::ThumbnailMetaData::ThumbnailMetaData(
897 const base::Time& current_time,
899 : capture_time_(current_time), url_(url) {
902 std::pair<SkBitmap, float> ThumbnailStore::CreateApproximation(
903 const SkBitmap& bitmap,
905 DCHECK(!bitmap.empty());
907 SkAutoLockPixels bitmap_lock(bitmap);
908 float new_scale = 1.f / kApproximationScaleFactor;
910 gfx::Size dst_size = gfx::ToFlooredSize(
911 gfx::ScaleSize(gfx::Size(bitmap.width(), bitmap.height()), new_scale));
913 dst_bitmap.allocPixels(SkImageInfo::Make(dst_size.width(),
915 bitmap.info().fColorType,
916 bitmap.info().fAlphaType));
917 dst_bitmap.eraseColor(0);
918 SkAutoLockPixels dst_bitmap_lock(dst_bitmap);
920 SkCanvas canvas(dst_bitmap);
921 canvas.scale(new_scale, new_scale);
922 canvas.drawBitmap(bitmap, 0, 0, NULL);
923 dst_bitmap.setImmutable();
925 return std::make_pair(dst_bitmap, new_scale * scale);