Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / android / thumbnail / thumbnail_cache.cc
blobb484ca897980c71eab77647916913d69aa7ec0f4
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_cache.h"
7 #include <algorithm>
8 #include <cmath>
10 #include "base/android/path_utils.h"
11 #include "base/big_endian.h"
12 #include "base/files/file.h"
13 #include "base/files/file_enumerator.h"
14 #include "base/files/file_path.h"
15 #include "base/files/file_util.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/threading/worker_pool.h"
18 #include "base/time/time.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/android/resources/ui_resource_provider.h"
27 #include "ui/gfx/android/device_display_info.h"
28 #include "ui/gfx/geometry/size_conversions.h"
30 namespace {
32 const float kApproximationScaleFactor = 4.f;
33 const base::TimeDelta kCaptureMinRequestTimeMs(
34 base::TimeDelta::FromMilliseconds(1000));
36 const int kCompressedKey = 0xABABABAB;
37 const int kCurrentExtraVersion = 1;
39 // Indicates whether we prefer to have more free CPU memory over GPU memory.
40 const bool kPreferCPUMemory = true;
42 size_t NextPowerOfTwo(size_t x) {
43 --x;
44 x |= x >> 1;
45 x |= x >> 2;
46 x |= x >> 4;
47 x |= x >> 8;
48 x |= x >> 16;
49 return x + 1;
52 size_t RoundUpMod4(size_t x) {
53 return (x + 3) & ~3;
56 gfx::Size GetEncodedSize(const gfx::Size& bitmap_size, bool supports_npot) {
57 DCHECK(!bitmap_size.IsEmpty());
58 if (!supports_npot)
59 return gfx::Size(NextPowerOfTwo(bitmap_size.width()),
60 NextPowerOfTwo(bitmap_size.height()));
61 else
62 return gfx::Size(RoundUpMod4(bitmap_size.width()),
63 RoundUpMod4(bitmap_size.height()));
66 template<typename T>
67 bool ReadBigEndianFromFile(base::File& file, T* out) {
68 char buffer[sizeof(T)];
69 if (file.ReadAtCurrentPos(buffer, sizeof(T)) != sizeof(T))
70 return false;
71 base::ReadBigEndian(buffer, out);
72 return true;
75 template<typename T>
76 bool WriteBigEndianToFile(base::File& file, T val) {
77 char buffer[sizeof(T)];
78 base::WriteBigEndian(buffer, val);
79 return file.WriteAtCurrentPos(buffer, sizeof(T)) == sizeof(T);
82 bool ReadBigEndianFloatFromFile(base::File& file, float* out) {
83 char buffer[sizeof(float)];
84 if (file.ReadAtCurrentPos(buffer, sizeof(buffer)) != sizeof(buffer))
85 return false;
87 #if defined(ARCH_CPU_LITTLE_ENDIAN)
88 for (size_t i = 0; i < sizeof(float) / 2; i++) {
89 char tmp = buffer[i];
90 buffer[i] = buffer[sizeof(float) - 1 - i];
91 buffer[sizeof(float) - 1 - i] = tmp;
93 #endif
94 memcpy(out, buffer, sizeof(buffer));
96 return true;
99 bool WriteBigEndianFloatToFile(base::File& file, float val) {
100 char buffer[sizeof(float)];
101 memcpy(buffer, &val, sizeof(buffer));
103 #if defined(ARCH_CPU_LITTLE_ENDIAN)
104 for (size_t i = 0; i < sizeof(float) / 2; i++) {
105 char tmp = buffer[i];
106 buffer[i] = buffer[sizeof(float) - 1 - i];
107 buffer[sizeof(float) - 1 - i] = tmp;
109 #endif
110 return file.WriteAtCurrentPos(buffer, sizeof(buffer)) == sizeof(buffer);
113 } // anonymous namespace
115 ThumbnailCache::ThumbnailCache(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 : compression_queue_max_size_(compression_queue_max_size),
121 write_queue_max_size_(write_queue_max_size),
122 use_approximation_thumbnail_(use_approximation_thumbnail),
123 compression_tasks_count_(0),
124 write_tasks_count_(0),
125 read_in_progress_(false),
126 cache_(default_cache_size),
127 approximation_cache_(approximation_cache_size),
128 ui_resource_provider_(NULL),
129 weak_factory_(this) {
130 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
133 ThumbnailCache::~ThumbnailCache() {
134 SetUIResourceProvider(NULL);
137 void ThumbnailCache::SetUIResourceProvider(
138 ui::UIResourceProvider* ui_resource_provider) {
139 if (ui_resource_provider_ == ui_resource_provider)
140 return;
142 approximation_cache_.Clear();
143 cache_.Clear();
145 ui_resource_provider_ = ui_resource_provider;
148 void ThumbnailCache::AddThumbnailCacheObserver(
149 ThumbnailCacheObserver* observer) {
150 if (!observers_.HasObserver(observer))
151 observers_.AddObserver(observer);
154 void ThumbnailCache::RemoveThumbnailCacheObserver(
155 ThumbnailCacheObserver* observer) {
156 if (observers_.HasObserver(observer))
157 observers_.RemoveObserver(observer);
160 void ThumbnailCache::Put(TabId tab_id,
161 const SkBitmap& bitmap,
162 float thumbnail_scale) {
163 if (!ui_resource_provider_ || bitmap.empty() || thumbnail_scale <= 0)
164 return;
166 DCHECK(thumbnail_meta_data_.find(tab_id) != thumbnail_meta_data_.end());
168 base::Time time_stamp = thumbnail_meta_data_[tab_id].capture_time();
169 scoped_ptr<Thumbnail> thumbnail = Thumbnail::Create(
170 tab_id, time_stamp, thumbnail_scale, ui_resource_provider_, this);
171 thumbnail->SetBitmap(bitmap);
173 RemoveFromReadQueue(tab_id);
174 MakeSpaceForNewItemIfNecessary(tab_id);
175 cache_.Put(tab_id, thumbnail.Pass());
177 if (use_approximation_thumbnail_) {
178 std::pair<SkBitmap, float> approximation =
179 CreateApproximation(bitmap, thumbnail_scale);
180 scoped_ptr<Thumbnail> approx_thumbnail = Thumbnail::Create(
181 tab_id, time_stamp, approximation.second, ui_resource_provider_, this);
182 approx_thumbnail->SetBitmap(approximation.first);
183 approximation_cache_.Put(tab_id, approx_thumbnail.Pass());
185 CompressThumbnailIfNecessary(tab_id, time_stamp, bitmap, thumbnail_scale);
188 void ThumbnailCache::Remove(TabId tab_id) {
189 cache_.Remove(tab_id);
190 approximation_cache_.Remove(tab_id);
191 thumbnail_meta_data_.erase(tab_id);
192 RemoveFromDisk(tab_id);
193 RemoveFromReadQueue(tab_id);
196 Thumbnail* ThumbnailCache::Get(TabId tab_id,
197 bool force_disk_read,
198 bool allow_approximation) {
199 Thumbnail* thumbnail = cache_.Get(tab_id);
200 if (thumbnail) {
201 thumbnail->CreateUIResource();
202 return thumbnail;
205 if (force_disk_read &&
206 std::find(visible_ids_.begin(), visible_ids_.end(), tab_id) !=
207 visible_ids_.end() &&
208 std::find(read_queue_.begin(), read_queue_.end(), tab_id) ==
209 read_queue_.end()) {
210 read_queue_.push_back(tab_id);
211 ReadNextThumbnail();
214 if (allow_approximation) {
215 thumbnail = approximation_cache_.Get(tab_id);
216 if (thumbnail) {
217 thumbnail->CreateUIResource();
218 return thumbnail;
222 return NULL;
225 void ThumbnailCache::RemoveFromDiskAtAndAboveId(TabId min_id) {
226 base::Closure remove_task =
227 base::Bind(&ThumbnailCache::RemoveFromDiskAtAndAboveIdTask,
228 min_id);
229 content::BrowserThread::PostTask(
230 content::BrowserThread::FILE, FROM_HERE, remove_task);
233 void ThumbnailCache::InvalidateThumbnailIfChanged(TabId tab_id,
234 const GURL& url) {
235 ThumbnailMetaDataMap::iterator meta_data_iter =
236 thumbnail_meta_data_.find(tab_id);
237 if (meta_data_iter == thumbnail_meta_data_.end()) {
238 thumbnail_meta_data_[tab_id] = ThumbnailMetaData(base::Time(), url);
239 } else if (meta_data_iter->second.url() != url) {
240 Remove(tab_id);
244 base::FilePath ThumbnailCache::GetCacheDirectory() {
245 base::FilePath path;
246 base::android::GetThumbnailCacheDirectory(&path);
247 return path;
250 base::FilePath ThumbnailCache::GetFilePath(TabId tab_id) {
251 base::FilePath path = GetCacheDirectory();
252 return path.Append(base::IntToString(tab_id));
255 bool ThumbnailCache::CheckAndUpdateThumbnailMetaData(TabId tab_id,
256 const GURL& url) {
257 base::Time current_time = base::Time::Now();
258 ThumbnailMetaDataMap::iterator meta_data_iter =
259 thumbnail_meta_data_.find(tab_id);
260 if (meta_data_iter != thumbnail_meta_data_.end() &&
261 meta_data_iter->second.url() == url &&
262 (current_time - meta_data_iter->second.capture_time()) <
263 kCaptureMinRequestTimeMs) {
264 return false;
267 thumbnail_meta_data_[tab_id] = ThumbnailMetaData(current_time, url);
268 return true;
271 void ThumbnailCache::UpdateVisibleIds(const TabIdList& priority) {
272 if (priority.empty()) {
273 visible_ids_.clear();
274 return;
277 size_t ids_size = std::min(priority.size(), cache_.MaximumCacheSize());
278 if (visible_ids_.size() == ids_size) {
279 // Early out if called with the same input as last time (We only care
280 // about the first mCache.MaximumCacheSize() entries).
281 bool needs_update = false;
282 TabIdList::const_iterator visible_iter = visible_ids_.begin();
283 TabIdList::const_iterator priority_iter = priority.begin();
284 while (visible_iter != visible_ids_.end() &&
285 priority_iter != priority.end()) {
286 if (*priority_iter != *visible_iter || !cache_.Get(*priority_iter)) {
287 needs_update = true;
288 break;
290 visible_iter++;
291 priority_iter++;
294 if (!needs_update)
295 return;
298 read_queue_.clear();
299 visible_ids_.clear();
300 size_t count = 0;
301 TabIdList::const_iterator iter = priority.begin();
302 while (iter != priority.end() && count < ids_size) {
303 TabId tab_id = *iter;
304 visible_ids_.push_back(tab_id);
305 if (!cache_.Get(tab_id) &&
306 std::find(read_queue_.begin(), read_queue_.end(), tab_id) ==
307 read_queue_.end()) {
308 read_queue_.push_back(tab_id);
310 iter++;
311 count++;
314 ReadNextThumbnail();
317 void ThumbnailCache::DecompressThumbnailFromFile(
318 TabId tab_id,
319 const base::Callback<void(bool, SkBitmap)>&
320 post_decompress_callback) {
321 base::Callback<void(skia::RefPtr<SkPixelRef>, float, const gfx::Size&)>
322 decompress_task = base::Bind(
323 &ThumbnailCache::DecompressionTask, post_decompress_callback);
325 content::BrowserThread::PostTask(
326 content::BrowserThread::FILE,
327 FROM_HERE,
328 base::Bind(&ThumbnailCache::ReadTask, true, tab_id, decompress_task));
331 void ThumbnailCache::RemoveFromDisk(TabId tab_id) {
332 base::Closure task =
333 base::Bind(&ThumbnailCache::RemoveFromDiskTask, tab_id);
334 content::BrowserThread::PostTask(
335 content::BrowserThread::FILE, FROM_HERE, task);
338 void ThumbnailCache::RemoveFromDiskTask(TabId tab_id) {
339 base::FilePath file_path = GetFilePath(tab_id);
340 if (base::PathExists(file_path))
341 base::DeleteFile(file_path, false);
344 void ThumbnailCache::RemoveFromDiskAtAndAboveIdTask(TabId min_id) {
345 base::FilePath dir_path = GetCacheDirectory();
346 base::FileEnumerator enumerator(dir_path, false, base::FileEnumerator::FILES);
347 while (true) {
348 base::FilePath path = enumerator.Next();
349 if (path.empty())
350 break;
351 base::FileEnumerator::FileInfo info = enumerator.GetInfo();
352 TabId tab_id;
353 bool success = base::StringToInt(info.GetName().value(), &tab_id);
354 if (success && tab_id >= min_id)
355 base::DeleteFile(path, false);
359 void ThumbnailCache::WriteThumbnailIfNecessary(
360 TabId tab_id,
361 skia::RefPtr<SkPixelRef> compressed_data,
362 float scale,
363 const gfx::Size& content_size) {
364 if (write_tasks_count_ >= write_queue_max_size_)
365 return;
367 write_tasks_count_++;
369 base::Callback<void()> post_write_task =
370 base::Bind(&ThumbnailCache::PostWriteTask, weak_factory_.GetWeakPtr());
371 content::BrowserThread::PostTask(content::BrowserThread::FILE,
372 FROM_HERE,
373 base::Bind(&ThumbnailCache::WriteTask,
374 tab_id,
375 compressed_data,
376 scale,
377 content_size,
378 post_write_task));
381 void ThumbnailCache::CompressThumbnailIfNecessary(
382 TabId tab_id,
383 const base::Time& time_stamp,
384 const SkBitmap& bitmap,
385 float scale) {
386 if (compression_tasks_count_ >= compression_queue_max_size_) {
387 RemoveOnMatchedTimeStamp(tab_id, time_stamp);
388 return;
391 compression_tasks_count_++;
393 base::Callback<void(skia::RefPtr<SkPixelRef>, const gfx::Size&)>
394 post_compression_task = base::Bind(&ThumbnailCache::PostCompressionTask,
395 weak_factory_.GetWeakPtr(),
396 tab_id,
397 time_stamp,
398 scale);
400 gfx::Size raw_data_size(bitmap.width(), bitmap.height());
401 gfx::Size encoded_size = GetEncodedSize(
402 raw_data_size, ui_resource_provider_->SupportsETC1NonPowerOfTwo());
404 base::WorkerPool::PostTask(FROM_HERE,
405 base::Bind(&ThumbnailCache::CompressionTask,
406 bitmap,
407 encoded_size,
408 post_compression_task),
409 true);
412 void ThumbnailCache::ReadNextThumbnail() {
413 if (read_queue_.empty() || read_in_progress_)
414 return;
416 TabId tab_id = read_queue_.front();
417 read_in_progress_ = true;
419 base::Callback<void(skia::RefPtr<SkPixelRef>, float, const gfx::Size&)>
420 post_read_task = base::Bind(
421 &ThumbnailCache::PostReadTask, weak_factory_.GetWeakPtr(), tab_id);
423 content::BrowserThread::PostTask(
424 content::BrowserThread::FILE,
425 FROM_HERE,
426 base::Bind(&ThumbnailCache::ReadTask, false, tab_id, post_read_task));
429 void ThumbnailCache::MakeSpaceForNewItemIfNecessary(TabId tab_id) {
430 if (cache_.Get(tab_id) ||
431 std::find(visible_ids_.begin(), visible_ids_.end(), tab_id) ==
432 visible_ids_.end() ||
433 cache_.size() < cache_.MaximumCacheSize()) {
434 return;
437 TabId key_to_remove;
438 bool found_key_to_remove = false;
440 // 1. Find a cached item not in this list
441 for (ExpiringThumbnailCache::iterator iter = cache_.begin();
442 iter != cache_.end();
443 iter++) {
444 if (std::find(visible_ids_.begin(), visible_ids_.end(), iter->first) ==
445 visible_ids_.end()) {
446 key_to_remove = iter->first;
447 found_key_to_remove = true;
448 break;
452 if (!found_key_to_remove) {
453 // 2. Find the least important id we can remove.
454 for (TabIdList::reverse_iterator riter = visible_ids_.rbegin();
455 riter != visible_ids_.rend();
456 riter++) {
457 if (cache_.Get(*riter)) {
458 key_to_remove = *riter;
459 break;
460 found_key_to_remove = true;
465 if (found_key_to_remove)
466 cache_.Remove(key_to_remove);
469 void ThumbnailCache::RemoveFromReadQueue(TabId tab_id) {
470 TabIdList::iterator read_iter =
471 std::find(read_queue_.begin(), read_queue_.end(), tab_id);
472 if (read_iter != read_queue_.end())
473 read_queue_.erase(read_iter);
476 void ThumbnailCache::InvalidateCachedThumbnail(Thumbnail* thumbnail) {
477 DCHECK(thumbnail);
478 TabId tab_id = thumbnail->tab_id();
479 cc::UIResourceId uid = thumbnail->ui_resource_id();
481 Thumbnail* cached_thumbnail = cache_.Get(tab_id);
482 if (cached_thumbnail && cached_thumbnail->ui_resource_id() == uid)
483 cache_.Remove(tab_id);
485 cached_thumbnail = approximation_cache_.Get(tab_id);
486 if (cached_thumbnail && cached_thumbnail->ui_resource_id() == uid)
487 approximation_cache_.Remove(tab_id);
490 namespace {
492 bool WriteToFile(base::File& file,
493 const gfx::Size& content_size,
494 const float scale,
495 skia::RefPtr<SkPixelRef> compressed_data) {
496 if (!file.IsValid())
497 return false;
499 if (!WriteBigEndianToFile(file, kCompressedKey))
500 return false;
502 if (!WriteBigEndianToFile(file, content_size.width()))
503 return false;
505 if (!WriteBigEndianToFile(file, content_size.height()))
506 return false;
508 // Write ETC1 header.
509 compressed_data->lockPixels();
511 unsigned char etc1_buffer[ETC_PKM_HEADER_SIZE];
512 etc1_pkm_format_header(etc1_buffer,
513 compressed_data->info().width(),
514 compressed_data->info().height());
516 int header_bytes_written = file.WriteAtCurrentPos(
517 reinterpret_cast<char*>(etc1_buffer), ETC_PKM_HEADER_SIZE);
518 if (header_bytes_written != ETC_PKM_HEADER_SIZE)
519 return false;
521 int data_size = etc1_get_encoded_data_size(
522 compressed_data->info().width(),
523 compressed_data->info().height());
524 int pixel_bytes_written = file.WriteAtCurrentPos(
525 reinterpret_cast<char*>(compressed_data->pixels()),
526 data_size);
527 if (pixel_bytes_written != data_size)
528 return false;
530 compressed_data->unlockPixels();
532 if (!WriteBigEndianToFile(file, kCurrentExtraVersion))
533 return false;
535 if (!WriteBigEndianFloatToFile(file, 1.f / scale))
536 return false;
538 return true;
541 } // anonymous namespace
543 void ThumbnailCache::WriteTask(TabId tab_id,
544 skia::RefPtr<SkPixelRef> compressed_data,
545 float scale,
546 const gfx::Size& content_size,
547 const base::Callback<void()>& post_write_task) {
548 DCHECK(compressed_data);
550 base::FilePath file_path = GetFilePath(tab_id);
552 base::File file(file_path,
553 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
555 bool success = WriteToFile(file,
556 content_size,
557 scale,
558 compressed_data);
560 file.Close();
562 if (!success)
563 base::DeleteFile(file_path, false);
565 content::BrowserThread::PostTask(
566 content::BrowserThread::UI, FROM_HERE, post_write_task);
569 void ThumbnailCache::PostWriteTask() {
570 write_tasks_count_--;
573 void ThumbnailCache::CompressionTask(
574 SkBitmap raw_data,
575 gfx::Size encoded_size,
576 const base::Callback<void(skia::RefPtr<SkPixelRef>, const gfx::Size&)>&
577 post_compression_task) {
578 skia::RefPtr<SkPixelRef> compressed_data;
579 gfx::Size content_size;
581 if (!raw_data.empty()) {
582 SkAutoLockPixels raw_data_lock(raw_data);
583 gfx::Size raw_data_size(raw_data.width(), raw_data.height());
584 size_t pixel_size = 4; // Pixel size is 4 bytes for kARGB_8888_Config.
585 size_t stride = pixel_size * raw_data_size.width();
587 size_t encoded_bytes =
588 etc1_get_encoded_data_size(encoded_size.width(), encoded_size.height());
589 SkImageInfo info = SkImageInfo::Make(encoded_size.width(),
590 encoded_size.height(),
591 kUnknown_SkColorType,
592 kUnpremul_SkAlphaType);
593 skia::RefPtr<SkData> etc1_pixel_data = skia::AdoptRef(
594 SkData::NewUninitialized(encoded_bytes));
595 skia::RefPtr<SkMallocPixelRef> etc1_pixel_ref = skia::AdoptRef(
596 SkMallocPixelRef::NewWithData(info, 0, NULL, etc1_pixel_data.get()));
598 etc1_pixel_ref->lockPixels();
599 bool success = etc1_encode_image(
600 reinterpret_cast<unsigned char*>(raw_data.getPixels()),
601 raw_data_size.width(),
602 raw_data_size.height(),
603 pixel_size,
604 stride,
605 reinterpret_cast<unsigned char*>(etc1_pixel_ref->pixels()),
606 encoded_size.width(),
607 encoded_size.height());
608 etc1_pixel_ref->setImmutable();
609 etc1_pixel_ref->unlockPixels();
611 if (success) {
612 compressed_data = etc1_pixel_ref;
613 content_size = raw_data_size;
617 content::BrowserThread::PostTask(
618 content::BrowserThread::UI,
619 FROM_HERE,
620 base::Bind(post_compression_task, compressed_data, content_size));
623 void ThumbnailCache::PostCompressionTask(
624 TabId tab_id,
625 const base::Time& time_stamp,
626 float scale,
627 skia::RefPtr<SkPixelRef> compressed_data,
628 const gfx::Size& content_size) {
629 compression_tasks_count_--;
630 if (!compressed_data) {
631 RemoveOnMatchedTimeStamp(tab_id, time_stamp);
632 return;
635 Thumbnail* thumbnail = cache_.Get(tab_id);
636 if (thumbnail) {
637 if (thumbnail->time_stamp() != time_stamp)
638 return;
639 thumbnail->SetCompressedBitmap(compressed_data, content_size);
640 thumbnail->CreateUIResource();
642 WriteThumbnailIfNecessary(tab_id, compressed_data, scale, content_size);
645 namespace {
647 bool ReadFromFile(base::File& file,
648 gfx::Size* out_content_size,
649 float* out_scale,
650 skia::RefPtr<SkPixelRef>* out_pixels) {
651 if (!file.IsValid())
652 return false;
654 int key = 0;
655 if (!ReadBigEndianFromFile(file, &key))
656 return false;
658 if (key != kCompressedKey)
659 return false;
661 int content_width = 0;
662 if (!ReadBigEndianFromFile(file, &content_width) || content_width <= 0)
663 return false;
665 int content_height = 0;
666 if (!ReadBigEndianFromFile(file, &content_height) || content_height <= 0)
667 return false;
669 out_content_size->SetSize(content_width, content_height);
671 // Read ETC1 header.
672 int header_bytes_read = 0;
673 unsigned char etc1_buffer[ETC_PKM_HEADER_SIZE];
674 header_bytes_read = file.ReadAtCurrentPos(
675 reinterpret_cast<char*>(etc1_buffer),
676 ETC_PKM_HEADER_SIZE);
677 if (header_bytes_read != ETC_PKM_HEADER_SIZE)
678 return false;
680 if (!etc1_pkm_is_valid(etc1_buffer))
681 return false;
683 int raw_width = 0;
684 raw_width = etc1_pkm_get_width(etc1_buffer);
685 if (raw_width <= 0)
686 return false;
688 int raw_height = 0;
689 raw_height = etc1_pkm_get_height(etc1_buffer);
690 if (raw_height <= 0)
691 return false;
693 // Do some simple sanity check validation. We can't have thumbnails larger
694 // than the max display size of the screen. We also can't have etc1 texture
695 // data larger than the next power of 2 up from that.
696 gfx::DeviceDisplayInfo display_info;
697 int max_dimension = std::max(display_info.GetDisplayWidth(),
698 display_info.GetDisplayHeight());
700 if (content_width > max_dimension
701 || content_height > max_dimension
702 || static_cast<size_t>(raw_width) > NextPowerOfTwo(max_dimension)
703 || static_cast<size_t>(raw_height) > NextPowerOfTwo(max_dimension)) {
704 return false;
707 int data_size = etc1_get_encoded_data_size(raw_width, raw_height);
708 skia::RefPtr<SkData> etc1_pixel_data =
709 skia::AdoptRef(SkData::NewUninitialized(data_size));
711 int pixel_bytes_read = file.ReadAtCurrentPos(
712 reinterpret_cast<char*>(etc1_pixel_data->writable_data()),
713 data_size);
715 if (pixel_bytes_read != data_size)
716 return false;
718 SkImageInfo info = SkImageInfo::Make(raw_width,
719 raw_height,
720 kUnknown_SkColorType,
721 kUnpremul_SkAlphaType);
723 *out_pixels = skia::AdoptRef(
724 SkMallocPixelRef::NewWithData(info,
726 NULL,
727 etc1_pixel_data.get()));
729 int extra_data_version = 0;
730 if (!ReadBigEndianFromFile(file, &extra_data_version))
731 return false;
733 *out_scale = 1.f;
734 if (extra_data_version == 1) {
735 if (!ReadBigEndianFloatFromFile(file, out_scale))
736 return false;
738 if (*out_scale == 0.f)
739 return false;
741 *out_scale = 1.f / *out_scale;
744 return true;
747 }// anonymous namespace
749 void ThumbnailCache::ReadTask(
750 bool decompress,
751 TabId tab_id,
752 const base::Callback<
753 void(skia::RefPtr<SkPixelRef>, float, const gfx::Size&)>&
754 post_read_task) {
755 gfx::Size content_size;
756 float scale = 0.f;
757 skia::RefPtr<SkPixelRef> compressed_data;
758 base::FilePath file_path = GetFilePath(tab_id);
760 if (base::PathExists(file_path)) {
761 base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
764 bool valid_contents = ReadFromFile(file,
765 &content_size,
766 &scale,
767 &compressed_data);
768 file.Close();
770 if (!valid_contents) {
771 content_size.SetSize(0, 0);
772 scale = 0.f;
773 compressed_data.clear();
774 base::DeleteFile(file_path, false);
778 if (decompress) {
779 base::WorkerPool::PostTask(
780 FROM_HERE,
781 base::Bind(post_read_task, compressed_data, scale, content_size),
782 true);
783 } else {
784 content::BrowserThread::PostTask(
785 content::BrowserThread::UI,
786 FROM_HERE,
787 base::Bind(post_read_task, compressed_data, scale, content_size));
791 void ThumbnailCache::PostReadTask(TabId tab_id,
792 skia::RefPtr<SkPixelRef> compressed_data,
793 float scale,
794 const gfx::Size& content_size) {
795 read_in_progress_ = false;
797 TabIdList::iterator iter =
798 std::find(read_queue_.begin(), read_queue_.end(), tab_id);
799 if (iter == read_queue_.end()) {
800 ReadNextThumbnail();
801 return;
804 read_queue_.erase(iter);
806 if (!cache_.Get(tab_id) && compressed_data) {
807 ThumbnailMetaDataMap::iterator meta_iter =
808 thumbnail_meta_data_.find(tab_id);
809 base::Time time_stamp = base::Time::Now();
810 if (meta_iter != thumbnail_meta_data_.end())
811 time_stamp = meta_iter->second.capture_time();
813 MakeSpaceForNewItemIfNecessary(tab_id);
814 scoped_ptr<Thumbnail> thumbnail = Thumbnail::Create(
815 tab_id, time_stamp, scale, ui_resource_provider_, this);
816 thumbnail->SetCompressedBitmap(compressed_data,
817 content_size);
818 if (kPreferCPUMemory)
819 thumbnail->CreateUIResource();
821 cache_.Put(tab_id, thumbnail.Pass());
822 NotifyObserversOfThumbnailRead(tab_id);
825 ReadNextThumbnail();
828 void ThumbnailCache::NotifyObserversOfThumbnailRead(TabId tab_id) {
829 FOR_EACH_OBSERVER(
830 ThumbnailCacheObserver, observers_, OnFinishedThumbnailRead(tab_id));
833 void ThumbnailCache::RemoveOnMatchedTimeStamp(TabId tab_id,
834 const base::Time& time_stamp) {
835 // We remove the cached version if it matches the tab_id and the time_stamp.
836 Thumbnail* thumbnail = cache_.Get(tab_id);
837 Thumbnail* approx_thumbnail = approximation_cache_.Get(tab_id);
838 if ((thumbnail && thumbnail->time_stamp() == time_stamp) ||
839 (approx_thumbnail && approx_thumbnail->time_stamp() == time_stamp)) {
840 Remove(tab_id);
842 return;
845 void ThumbnailCache::DecompressionTask(
846 const base::Callback<void(bool, SkBitmap)>&
847 post_decompression_callback,
848 skia::RefPtr<SkPixelRef> compressed_data,
849 float scale,
850 const gfx::Size& content_size) {
851 SkBitmap raw_data_small;
852 bool success = false;
854 if (compressed_data.get()) {
855 gfx::Size buffer_size = gfx::Size(compressed_data->info().width(),
856 compressed_data->info().height());
858 SkBitmap raw_data;
859 raw_data.allocPixels(SkImageInfo::Make(buffer_size.width(),
860 buffer_size.height(),
861 kRGBA_8888_SkColorType,
862 kOpaque_SkAlphaType));
863 SkAutoLockPixels raw_data_lock(raw_data);
864 compressed_data->lockPixels();
865 success = etc1_decode_image(
866 reinterpret_cast<unsigned char*>(compressed_data->pixels()),
867 reinterpret_cast<unsigned char*>(raw_data.getPixels()),
868 buffer_size.width(),
869 buffer_size.height(),
870 raw_data.bytesPerPixel(),
871 raw_data.rowBytes());
872 compressed_data->unlockPixels();
873 raw_data.setImmutable();
875 if (!success) {
876 // Leave raw_data_small empty for consistency with other failure modes.
877 } else if (content_size == buffer_size) {
878 // Shallow copy the pixel reference.
879 raw_data_small = raw_data;
880 } else {
881 // The content size is smaller than the buffer size (likely because of
882 // a power-of-two rounding), so deep copy the bitmap.
883 raw_data_small.allocPixels(SkImageInfo::Make(content_size.width(),
884 content_size.height(),
885 kRGBA_8888_SkColorType,
886 kOpaque_SkAlphaType));
887 SkAutoLockPixels raw_data_small_lock(raw_data_small);
888 SkCanvas small_canvas(raw_data_small);
889 small_canvas.drawBitmap(raw_data, 0, 0);
890 raw_data_small.setImmutable();
894 content::BrowserThread::PostTask(
895 content::BrowserThread::UI,
896 FROM_HERE,
897 base::Bind(post_decompression_callback, success, raw_data_small));
900 ThumbnailCache::ThumbnailMetaData::ThumbnailMetaData() {
903 ThumbnailCache::ThumbnailMetaData::ThumbnailMetaData(
904 const base::Time& current_time,
905 const GURL& url)
906 : capture_time_(current_time), url_(url) {
909 std::pair<SkBitmap, float> ThumbnailCache::CreateApproximation(
910 const SkBitmap& bitmap,
911 float scale) {
912 DCHECK(!bitmap.empty());
913 DCHECK_GT(scale, 0);
914 SkAutoLockPixels bitmap_lock(bitmap);
915 float new_scale = 1.f / kApproximationScaleFactor;
917 gfx::Size dst_size = gfx::ToFlooredSize(
918 gfx::ScaleSize(gfx::Size(bitmap.width(), bitmap.height()), new_scale));
919 SkBitmap dst_bitmap;
920 dst_bitmap.allocPixels(SkImageInfo::Make(dst_size.width(),
921 dst_size.height(),
922 bitmap.info().colorType(),
923 bitmap.info().alphaType()));
924 dst_bitmap.eraseColor(0);
925 SkAutoLockPixels dst_bitmap_lock(dst_bitmap);
927 SkCanvas canvas(dst_bitmap);
928 canvas.scale(new_scale, new_scale);
929 canvas.drawBitmap(bitmap, 0, 0, NULL);
930 dst_bitmap.setImmutable();
932 return std::make_pair(dst_bitmap, new_scale * scale);