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"
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"
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
) {
52 size_t RoundUpMod4(size_t x
) {
56 gfx::Size
GetEncodedSize(const gfx::Size
& bitmap_size
, bool supports_npot
) {
57 DCHECK(!bitmap_size
.IsEmpty());
59 return gfx::Size(NextPowerOfTwo(bitmap_size
.width()),
60 NextPowerOfTwo(bitmap_size
.height()));
62 return gfx::Size(RoundUpMod4(bitmap_size
.width()),
63 RoundUpMod4(bitmap_size
.height()));
67 bool ReadBigEndianFromFile(base::File
& file
, T
* out
) {
68 char buffer
[sizeof(T
)];
69 if (file
.ReadAtCurrentPos(buffer
, sizeof(T
)) != sizeof(T
))
71 base::ReadBigEndian(buffer
, out
);
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
))
87 #if defined(ARCH_CPU_LITTLE_ENDIAN)
88 for (size_t i
= 0; i
< sizeof(float) / 2; i
++) {
90 buffer
[i
] = buffer
[sizeof(float) - 1 - i
];
91 buffer
[sizeof(float) - 1 - i
] = tmp
;
94 memcpy(out
, buffer
, sizeof(buffer
));
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
;
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
)
142 approximation_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)
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
);
201 thumbnail
->CreateUIResource();
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
) ==
210 read_queue_
.push_back(tab_id
);
214 if (allow_approximation
) {
215 thumbnail
= approximation_cache_
.Get(tab_id
);
217 thumbnail
->CreateUIResource();
225 void ThumbnailCache::RemoveFromDiskAtAndAboveId(TabId min_id
) {
226 base::Closure remove_task
=
227 base::Bind(&ThumbnailCache::RemoveFromDiskAtAndAboveIdTask
,
229 content::BrowserThread::PostTask(
230 content::BrowserThread::FILE, FROM_HERE
, remove_task
);
233 void ThumbnailCache::InvalidateThumbnailIfChanged(TabId tab_id
,
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
) {
244 base::FilePath
ThumbnailCache::GetCacheDirectory() {
246 base::android::GetThumbnailCacheDirectory(&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
,
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
) {
267 thumbnail_meta_data_
[tab_id
] = ThumbnailMetaData(current_time
, url
);
271 void ThumbnailCache::UpdateVisibleIds(const TabIdList
& priority
) {
272 if (priority
.empty()) {
273 visible_ids_
.clear();
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
)) {
299 visible_ids_
.clear();
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
) ==
308 read_queue_
.push_back(tab_id
);
317 void ThumbnailCache::DecompressThumbnailFromFile(
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,
328 base::Bind(&ThumbnailCache::ReadTask
, true, tab_id
, decompress_task
));
331 void ThumbnailCache::RemoveFromDisk(TabId tab_id
) {
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
);
348 base::FilePath path
= enumerator
.Next();
351 base::FileEnumerator::FileInfo info
= enumerator
.GetInfo();
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(
361 skia::RefPtr
<SkPixelRef
> compressed_data
,
363 const gfx::Size
& content_size
) {
364 if (write_tasks_count_
>= write_queue_max_size_
)
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,
373 base::Bind(&ThumbnailCache::WriteTask
,
381 void ThumbnailCache::CompressThumbnailIfNecessary(
383 const base::Time
& time_stamp
,
384 const SkBitmap
& bitmap
,
386 if (compression_tasks_count_
>= compression_queue_max_size_
) {
387 RemoveOnMatchedTimeStamp(tab_id
, time_stamp
);
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(),
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
,
408 post_compression_task
),
412 void ThumbnailCache::ReadNextThumbnail() {
413 if (read_queue_
.empty() || read_in_progress_
)
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,
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()) {
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();
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;
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();
457 if (cache_
.Get(*riter
)) {
458 key_to_remove
= *riter
;
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
) {
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
);
492 bool WriteToFile(base::File
& file
,
493 const gfx::Size
& content_size
,
495 skia::RefPtr
<SkPixelRef
> compressed_data
) {
499 if (!WriteBigEndianToFile(file
, kCompressedKey
))
502 if (!WriteBigEndianToFile(file
, content_size
.width()))
505 if (!WriteBigEndianToFile(file
, content_size
.height()))
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
)
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()),
527 if (pixel_bytes_written
!= data_size
)
530 compressed_data
->unlockPixels();
532 if (!WriteBigEndianToFile(file
, kCurrentExtraVersion
))
535 if (!WriteBigEndianFloatToFile(file
, 1.f
/ scale
))
541 } // anonymous namespace
543 void ThumbnailCache::WriteTask(TabId tab_id
,
544 skia::RefPtr
<SkPixelRef
> compressed_data
,
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
,
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(
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(),
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();
612 compressed_data
= etc1_pixel_ref
;
613 content_size
= raw_data_size
;
617 content::BrowserThread::PostTask(
618 content::BrowserThread::UI
,
620 base::Bind(post_compression_task
, compressed_data
, content_size
));
623 void ThumbnailCache::PostCompressionTask(
625 const base::Time
& time_stamp
,
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
);
635 Thumbnail
* thumbnail
= cache_
.Get(tab_id
);
637 if (thumbnail
->time_stamp() != time_stamp
)
639 thumbnail
->SetCompressedBitmap(compressed_data
, content_size
);
640 thumbnail
->CreateUIResource();
642 WriteThumbnailIfNecessary(tab_id
, compressed_data
, scale
, content_size
);
647 bool ReadFromFile(base::File
& file
,
648 gfx::Size
* out_content_size
,
650 skia::RefPtr
<SkPixelRef
>* out_pixels
) {
655 if (!ReadBigEndianFromFile(file
, &key
))
658 if (key
!= kCompressedKey
)
661 int content_width
= 0;
662 if (!ReadBigEndianFromFile(file
, &content_width
) || content_width
<= 0)
665 int content_height
= 0;
666 if (!ReadBigEndianFromFile(file
, &content_height
) || content_height
<= 0)
669 out_content_size
->SetSize(content_width
, content_height
);
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
)
680 if (!etc1_pkm_is_valid(etc1_buffer
))
684 raw_width
= etc1_pkm_get_width(etc1_buffer
);
689 raw_height
= etc1_pkm_get_height(etc1_buffer
);
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
)) {
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()),
715 if (pixel_bytes_read
!= data_size
)
718 SkImageInfo info
= SkImageInfo::Make(raw_width
,
720 kUnknown_SkColorType
,
721 kUnpremul_SkAlphaType
);
723 *out_pixels
= skia::AdoptRef(
724 SkMallocPixelRef::NewWithData(info
,
727 etc1_pixel_data
.get()));
729 int extra_data_version
= 0;
730 if (!ReadBigEndianFromFile(file
, &extra_data_version
))
734 if (extra_data_version
== 1) {
735 if (!ReadBigEndianFloatFromFile(file
, out_scale
))
738 if (*out_scale
== 0.f
)
741 *out_scale
= 1.f
/ *out_scale
;
747 }// anonymous namespace
749 void ThumbnailCache::ReadTask(
752 const base::Callback
<
753 void(skia::RefPtr
<SkPixelRef
>, float, const gfx::Size
&)>&
755 gfx::Size content_size
;
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
,
770 if (!valid_contents
) {
771 content_size
.SetSize(0, 0);
773 compressed_data
.clear();
774 base::DeleteFile(file_path
, false);
779 base::WorkerPool::PostTask(
781 base::Bind(post_read_task
, compressed_data
, scale
, content_size
),
784 content::BrowserThread::PostTask(
785 content::BrowserThread::UI
,
787 base::Bind(post_read_task
, compressed_data
, scale
, content_size
));
791 void ThumbnailCache::PostReadTask(TabId tab_id
,
792 skia::RefPtr
<SkPixelRef
> compressed_data
,
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()) {
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
,
818 if (kPreferCPUMemory
)
819 thumbnail
->CreateUIResource();
821 cache_
.Put(tab_id
, thumbnail
.Pass());
822 NotifyObserversOfThumbnailRead(tab_id
);
828 void ThumbnailCache::NotifyObserversOfThumbnailRead(TabId tab_id
) {
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
)) {
845 void ThumbnailCache::DecompressionTask(
846 const base::Callback
<void(bool, SkBitmap
)>&
847 post_decompression_callback
,
848 skia::RefPtr
<SkPixelRef
> compressed_data
,
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());
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()),
869 buffer_size
.height(),
870 raw_data
.bytesPerPixel(),
871 raw_data
.rowBytes());
872 compressed_data
->unlockPixels();
873 raw_data
.setImmutable();
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
;
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
,
897 base::Bind(post_decompression_callback
, success
, raw_data_small
));
900 ThumbnailCache::ThumbnailMetaData::ThumbnailMetaData() {
903 ThumbnailCache::ThumbnailMetaData::ThumbnailMetaData(
904 const base::Time
& current_time
,
906 : capture_time_(current_time
), url_(url
) {
909 std::pair
<SkBitmap
, float> ThumbnailCache::CreateApproximation(
910 const SkBitmap
& bitmap
,
912 DCHECK(!bitmap
.empty());
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
));
920 dst_bitmap
.allocPixels(SkImageInfo::Make(dst_size
.width(),
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
);