1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/android/thumbnail/thumbnail_store.h" 6 7 #include <algorithm> 8 #include <cmath> 9 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" 28 29 namespace { 30 31 const float kApproximationScaleFactor = 4.f; 32 const base::TimeDelta kCaptureMinRequestTimeMs( 33 base::TimeDelta::FromMilliseconds(1000)); 34 35 const int kCompressedKey = 0xABABABAB; 36 const int kCurrentExtraVersion = 1; 37 38 // Indicates whether we prefer to have more free CPU memory over GPU memory. 39 const bool kPreferCPUMemory = true; 40 41 size_t NextPowerOfTwo(size_t x) { 42 --x; 43 x |= x >> 1; 44 x |= x >> 2; 45 x |= x >> 4; 46 x |= x >> 8; 47 x |= x >> 16; 48 return x + 1; 49 } 50 51 size_t RoundUpMod4(size_t x) { 52 return (x + 3) & ~3; 53 } 54 55 gfx::Size GetEncodedSize(const gfx::Size& bitmap_size, bool supports_npot) { 56 DCHECK(!bitmap_size.IsEmpty()); 57 if (!supports_npot) 58 return gfx::Size(NextPowerOfTwo(bitmap_size.width()), 59 NextPowerOfTwo(bitmap_size.height())); 60 else 61 return gfx::Size(RoundUpMod4(bitmap_size.width()), 62 RoundUpMod4(bitmap_size.height())); 63 } 64 65 template<typename T> 66 bool ReadBigEndianFromFile(base::File& file, T* out) { 67 char buffer[sizeof(T)]; 68 if (file.ReadAtCurrentPos(buffer, sizeof(T)) != sizeof(T)) 69 return false; 70 base::ReadBigEndian(buffer, out); 71 return true; 72 } 73 74 template<typename T> 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); 79 } 80 81 bool ReadBigEndianFloatFromFile(base::File& file, float* out) { 82 char buffer[sizeof(float)]; 83 if (file.ReadAtCurrentPos(buffer, sizeof(buffer)) != sizeof(buffer)) 84 return false; 85 86 #if defined(ARCH_CPU_LITTLE_ENDIAN) 87 for (size_t i = 0; i < sizeof(float) / 2; i++) { 88 char tmp = buffer[i]; 89 buffer[i] = buffer[sizeof(float) - 1 - i]; 90 buffer[sizeof(float) - 1 - i] = tmp; 91 } 92 #endif 93 memcpy(out, buffer, sizeof(buffer)); 94 95 return true; 96 } 97 98 bool WriteBigEndianFloatToFile(base::File& file, float val) { 99 char buffer[sizeof(float)]; 100 memcpy(buffer, &val, sizeof(buffer)); 101 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; 107 } 108 #endif 109 return file.WriteAtCurrentPos(buffer, sizeof(buffer)) == sizeof(buffer); 110 } 111 112 } // anonymous namespace 113 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)); 132 } 133 134 ThumbnailStore::~ThumbnailStore() { 135 SetUIResourceProvider(NULL); 136 } 137 138 void ThumbnailStore::SetUIResourceProvider( 139 content::UIResourceProvider* ui_resource_provider) { 140 if (ui_resource_provider_ == ui_resource_provider) 141 return; 142 143 approximation_cache_.Clear(); 144 cache_.Clear(); 145 146 ui_resource_provider_ = ui_resource_provider; 147 } 148 149 void ThumbnailStore::AddThumbnailStoreObserver( 150 ThumbnailStoreObserver* observer) { 151 if (!observers_.HasObserver(observer)) 152 observers_.AddObserver(observer); 153 } 154 155 void ThumbnailStore::RemoveThumbnailStoreObserver( 156 ThumbnailStoreObserver* observer) { 157 if (observers_.HasObserver(observer)) 158 observers_.RemoveObserver(observer); 159 } 160 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) 165 return; 166 167 DCHECK(thumbnail_meta_data_.find(tab_id) != thumbnail_meta_data_.end()); 168 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); 173 174 RemoveFromReadQueue(tab_id); 175 MakeSpaceForNewItemIfNecessary(tab_id); 176 cache_.Put(tab_id, thumbnail.Pass()); 177 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()); 185 } 186 CompressThumbnailIfNecessary(tab_id, time_stamp, bitmap, thumbnail_scale); 187 } 188 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); 195 } 196 197 Thumbnail* ThumbnailStore::Get(TabId tab_id, 198 bool force_disk_read, 199 bool allow_approximation) { 200 Thumbnail* thumbnail = cache_.Get(tab_id); 201 if (thumbnail) { 202 thumbnail->CreateUIResource(); 203 return thumbnail; 204 } 205 206 if (force_disk_read && 207 std::find(visible_ids_.begin(), visible_ids_.end(), tab_id) != 208 visible_ids_.end() && 209 std::find(read_queue_.begin(), read_queue_.end(), tab_id) == 210 read_queue_.end()) { 211 read_queue_.push_back(tab_id); 212 ReadNextThumbnail(); 213 } 214 215 if (allow_approximation) { 216 thumbnail = approximation_cache_.Get(tab_id); 217 if (thumbnail) { 218 thumbnail->CreateUIResource(); 219 return thumbnail; 220 } 221 } 222 223 return NULL; 224 } 225 226 void ThumbnailStore::RemoveFromDiskAtAndAboveId(TabId min_id) { 227 base::Closure remove_task = 228 base::Bind(&ThumbnailStore::RemoveFromDiskAtAndAboveIdTask, 229 disk_cache_path_, 230 min_id); 231 content::BrowserThread::PostTask( 232 content::BrowserThread::FILE, FROM_HERE, remove_task); 233 } 234 235 void ThumbnailStore::InvalidateThumbnailIfChanged(TabId tab_id, 236 const GURL& url) { 237 ThumbnailMetaDataMap::iterator meta_data_iter = 238 thumbnail_meta_data_.find(tab_id); 239 if (meta_data_iter == thumbnail_meta_data_.end()) { 240 thumbnail_meta_data_[tab_id] = ThumbnailMetaData(base::Time(), url); 241 } else if (meta_data_iter->second.url() != url) { 242 Remove(tab_id); 243 } 244 } 245 246 bool ThumbnailStore::CheckAndUpdateThumbnailMetaData(TabId tab_id, 247 const GURL& url) { 248 base::Time current_time = base::Time::Now(); 249 ThumbnailMetaDataMap::iterator meta_data_iter = 250 thumbnail_meta_data_.find(tab_id); 251 if (meta_data_iter != thumbnail_meta_data_.end() && 252 meta_data_iter->second.url() == url && 253 (current_time - meta_data_iter->second.capture_time()) < 254 kCaptureMinRequestTimeMs) { 255 return false; 256 } 257 258 thumbnail_meta_data_[tab_id] = ThumbnailMetaData(current_time, url); 259 return true; 260 } 261 262 void ThumbnailStore::UpdateVisibleIds(const TabIdList& priority) { 263 if (priority.empty()) { 264 visible_ids_.clear(); 265 return; 266 } 267 268 size_t ids_size = std::min(priority.size(), cache_.MaximumCacheSize()); 269 if (visible_ids_.size() == ids_size) { 270 // Early out if called with the same input as last time (We only care 271 // about the first mCache.MaximumCacheSize() entries). 272 bool lists_differ = false; 273 TabIdList::const_iterator visible_iter = visible_ids_.begin(); 274 TabIdList::const_iterator priority_iter = priority.begin(); 275 while (visible_iter != visible_ids_.end() && 276 priority_iter != priority.end()) { 277 if (*priority_iter != *visible_iter) { 278 lists_differ = true; 279 break; 280 } 281 visible_iter++; 282 priority_iter++; 283 } 284 285 if (!lists_differ) 286 return; 287 } 288 289 read_queue_.clear(); 290 visible_ids_.clear(); 291 size_t count = 0; 292 TabIdList::const_iterator iter = priority.begin(); 293 while (iter != priority.end() && count < ids_size) { 294 TabId tab_id = *iter; 295 visible_ids_.push_back(tab_id); 296 if (!cache_.Get(tab_id) && 297 std::find(read_queue_.begin(), read_queue_.end(), tab_id) == 298 read_queue_.end()) { 299 read_queue_.push_back(tab_id); 300 } 301 iter++; 302 count++; 303 } 304 305 ReadNextThumbnail(); 306 } 307 308 void ThumbnailStore::DecompressThumbnailFromFile( 309 TabId tab_id, 310 const base::Callback<void(bool, SkBitmap)>& 311 post_decompress_callback) { 312 base::FilePath file_path = GetFilePath(tab_id); 313 314 base::Callback<void(skia::RefPtr<SkPixelRef>, float, const gfx::Size&)> 315 decompress_task = base::Bind( 316 &ThumbnailStore::DecompressionTask, post_decompress_callback); 317 318 content::BrowserThread::PostTask( 319 content::BrowserThread::FILE, 320 FROM_HERE, 321 base::Bind(&ThumbnailStore::ReadTask, true, file_path, decompress_task)); 322 } 323 324 void ThumbnailStore::RemoveFromDisk(TabId tab_id) { 325 base::FilePath file_path = GetFilePath(tab_id); 326 base::Closure task = 327 base::Bind(&ThumbnailStore::RemoveFromDiskTask, file_path); 328 content::BrowserThread::PostTask( 329 content::BrowserThread::FILE, FROM_HERE, task); 330 } 331 332 void ThumbnailStore::RemoveFromDiskTask(const base::FilePath& file_path) { 333 if (base::PathExists(file_path)) 334 base::DeleteFile(file_path, false); 335 } 336 337 void ThumbnailStore::RemoveFromDiskAtAndAboveIdTask( 338 const base::FilePath& dir_path, 339 TabId min_id) { 340 base::FileEnumerator enumerator(dir_path, false, base::FileEnumerator::FILES); 341 while (true) { 342 base::FilePath path = enumerator.Next(); 343 if (path.empty()) 344 break; 345 base::FileEnumerator::FileInfo info = enumerator.GetInfo(); 346 TabId tab_id; 347 bool success = base::StringToInt(info.GetName().value(), &tab_id); 348 if (success && tab_id >= min_id) 349 base::DeleteFile(path, false); 350 } 351 } 352 353 void ThumbnailStore::WriteThumbnailIfNecessary( 354 TabId tab_id, 355 skia::RefPtr<SkPixelRef> compressed_data, 356 float scale, 357 const gfx::Size& content_size) { 358 if (write_tasks_count_ >= write_queue_max_size_) 359 return; 360 361 write_tasks_count_++; 362 363 base::Callback<void()> post_write_task = 364 base::Bind(&ThumbnailStore::PostWriteTask, weak_factory_.GetWeakPtr()); 365 content::BrowserThread::PostTask(content::BrowserThread::FILE, 366 FROM_HERE, 367 base::Bind(&ThumbnailStore::WriteTask, 368 GetFilePath(tab_id), 369 compressed_data, 370 scale, 371 content_size, 372 post_write_task)); 373 } 374 375 void ThumbnailStore::CompressThumbnailIfNecessary( 376 TabId tab_id, 377 const base::Time& time_stamp, 378 const SkBitmap& bitmap, 379 float scale) { 380 if (compression_tasks_count_ >= compression_queue_max_size_) { 381 RemoveOnMatchedTimeStamp(tab_id, time_stamp); 382 return; 383 } 384 385 compression_tasks_count_++; 386 387 base::Callback<void(skia::RefPtr<SkPixelRef>, const gfx::Size&)> 388 post_compression_task = base::Bind(&ThumbnailStore::PostCompressionTask, 389 weak_factory_.GetWeakPtr(), 390 tab_id, 391 time_stamp, 392 scale); 393 394 gfx::Size raw_data_size(bitmap.width(), bitmap.height()); 395 gfx::Size encoded_size = GetEncodedSize( 396 raw_data_size, ui_resource_provider_->SupportsETC1NonPowerOfTwo()); 397 398 base::WorkerPool::PostTask(FROM_HERE, 399 base::Bind(&ThumbnailStore::CompressionTask, 400 bitmap, 401 encoded_size, 402 post_compression_task), 403 true); 404 } 405 406 void ThumbnailStore::ReadNextThumbnail() { 407 if (read_queue_.empty() || read_in_progress_) 408 return; 409 410 TabId tab_id = read_queue_.front(); 411 read_in_progress_ = true; 412 413 base::FilePath file_path = GetFilePath(tab_id); 414 415 base::Callback<void(skia::RefPtr<SkPixelRef>, float, const gfx::Size&)> 416 post_read_task = base::Bind( 417 &ThumbnailStore::PostReadTask, weak_factory_.GetWeakPtr(), tab_id); 418 419 content::BrowserThread::PostTask( 420 content::BrowserThread::FILE, 421 FROM_HERE, 422 base::Bind(&ThumbnailStore::ReadTask, false, file_path, post_read_task)); 423 } 424 425 void ThumbnailStore::MakeSpaceForNewItemIfNecessary(TabId tab_id) { 426 if (cache_.Get(tab_id) || 427 std::find(visible_ids_.begin(), visible_ids_.end(), tab_id) == 428 visible_ids_.end() || 429 cache_.size() < cache_.MaximumCacheSize()) { 430 return; 431 } 432 433 TabId key_to_remove; 434 bool found_key_to_remove = false; 435 436 // 1. Find a cached item not in this list 437 for (ExpiringThumbnailCache::iterator iter = cache_.begin(); 438 iter != cache_.end(); 439 iter++) { 440 if (std::find(visible_ids_.begin(), visible_ids_.end(), iter->first) == 441 visible_ids_.end()) { 442 key_to_remove = iter->first; 443 found_key_to_remove = true; 444 break; 445 } 446 } 447 448 if (!found_key_to_remove) { 449 // 2. Find the least important id we can remove. 450 for (TabIdList::reverse_iterator riter = visible_ids_.rbegin(); 451 riter != visible_ids_.rend(); 452 riter++) { 453 if (cache_.Get(*riter)) { 454 key_to_remove = *riter; 455 break; 456 found_key_to_remove = true; 457 } 458 } 459 } 460 461 if (found_key_to_remove) 462 cache_.Remove(key_to_remove); 463 } 464 465 void ThumbnailStore::RemoveFromReadQueue(TabId tab_id) { 466 TabIdList::iterator read_iter = 467 std::find(read_queue_.begin(), read_queue_.end(), tab_id); 468 if (read_iter != read_queue_.end()) 469 read_queue_.erase(read_iter); 470 } 471 472 void ThumbnailStore::InvalidateCachedThumbnail(Thumbnail* thumbnail) { 473 DCHECK(thumbnail); 474 TabId tab_id = thumbnail->tab_id(); 475 cc::UIResourceId uid = thumbnail->ui_resource_id(); 476 477 Thumbnail* cached_thumbnail = cache_.Get(tab_id); 478 if (cached_thumbnail && cached_thumbnail->ui_resource_id() == uid) 479 cache_.Remove(tab_id); 480 481 cached_thumbnail = approximation_cache_.Get(tab_id); 482 if (cached_thumbnail && cached_thumbnail->ui_resource_id() == uid) 483 approximation_cache_.Remove(tab_id); 484 } 485 486 base::FilePath ThumbnailStore::GetFilePath(TabId tab_id) const { 487 return disk_cache_path_.Append(base::IntToString(tab_id)); 488 } 489 490 namespace { 491 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; 498 499 if (!WriteBigEndianToFile(file, kCompressedKey)) 500 return false; 501 502 if (!WriteBigEndianToFile(file, content_size.width())) 503 return false; 504 505 if (!WriteBigEndianToFile(file, content_size.height())) 506 return false; 507 508 // Write ETC1 header. 509 compressed_data->lockPixels(); 510 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()); 515 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; 520 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; 529 530 compressed_data->unlockPixels(); 531 532 if (!WriteBigEndianToFile(file, kCurrentExtraVersion)) 533 return false; 534 535 if (!WriteBigEndianFloatToFile(file, 1.f / scale)) 536 return false; 537 538 return true; 539 } 540 541 } // anonymous namespace 542 543 void ThumbnailStore::WriteTask(const base::FilePath& file_path, 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); 549 550 base::File file(file_path, 551 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); 552 553 bool success = WriteToFile(file, 554 content_size, 555 scale, 556 compressed_data); 557 558 file.Close(); 559 560 if (!success) 561 base::DeleteFile(file_path, false); 562 563 content::BrowserThread::PostTask( 564 content::BrowserThread::UI, FROM_HERE, post_write_task); 565 } 566 567 void ThumbnailStore::PostWriteTask() { 568 write_tasks_count_--; 569 } 570 571 void ThumbnailStore::CompressionTask( 572 SkBitmap raw_data, 573 gfx::Size encoded_size, 574 const base::Callback<void(skia::RefPtr<SkPixelRef>, const gfx::Size&)>& 575 post_compression_task) { 576 skia::RefPtr<SkPixelRef> compressed_data; 577 gfx::Size content_size; 578 579 if (!raw_data.empty()) { 580 SkAutoLockPixels raw_data_lock(raw_data); 581 gfx::Size raw_data_size(raw_data.width(), raw_data.height()); 582 size_t pixel_size = 4; // Pixel size is 4 bytes for kARGB_8888_Config. 583 size_t stride = pixel_size * raw_data_size.width(); 584 585 size_t encoded_bytes = 586 etc1_get_encoded_data_size(encoded_size.width(), encoded_size.height()); 587 SkImageInfo info = SkImageInfo::Make(encoded_size.width(), 588 encoded_size.height(), 589 kUnknown_SkColorType, 590 kUnpremul_SkAlphaType); 591 skia::RefPtr<SkData> etc1_pixel_data = skia::AdoptRef( 592 SkData::NewUninitialized(encoded_bytes)); 593 skia::RefPtr<SkMallocPixelRef> etc1_pixel_ref = skia::AdoptRef( 594 SkMallocPixelRef::NewWithData(info, 0, NULL, etc1_pixel_data.get())); 595 596 etc1_pixel_ref->lockPixels(); 597 bool success = etc1_encode_image( 598 reinterpret_cast<unsigned char*>(raw_data.getPixels()), 599 raw_data_size.width(), 600 raw_data_size.height(), 601 pixel_size, 602 stride, 603 reinterpret_cast<unsigned char*>(etc1_pixel_ref->pixels()), 604 encoded_size.width(), 605 encoded_size.height()); 606 etc1_pixel_ref->setImmutable(); 607 etc1_pixel_ref->unlockPixels(); 608 609 if (success) { 610 compressed_data = etc1_pixel_ref; 611 content_size = raw_data_size; 612 } 613 } 614 615 content::BrowserThread::PostTask( 616 content::BrowserThread::UI, 617 FROM_HERE, 618 base::Bind(post_compression_task, compressed_data, content_size)); 619 } 620 621 void ThumbnailStore::PostCompressionTask( 622 TabId tab_id, 623 const base::Time& time_stamp, 624 float scale, 625 skia::RefPtr<SkPixelRef> compressed_data, 626 const gfx::Size& content_size) { 627 compression_tasks_count_--; 628 if (!compressed_data) { 629 RemoveOnMatchedTimeStamp(tab_id, time_stamp); 630 return; 631 } 632 633 Thumbnail* thumbnail = cache_.Get(tab_id); 634 if (thumbnail) { 635 if (thumbnail->time_stamp() != time_stamp) 636 return; 637 thumbnail->SetCompressedBitmap(compressed_data, content_size); 638 thumbnail->CreateUIResource(); 639 } 640 WriteThumbnailIfNecessary(tab_id, compressed_data, scale, content_size); 641 } 642 643 namespace { 644 645 bool ReadFromFile(base::File& file, 646 gfx::Size* out_content_size, 647 float* out_scale, 648 skia::RefPtr<SkPixelRef>* out_pixels) { 649 if (!file.IsValid()) 650 return false; 651 652 int key = 0; 653 if (!ReadBigEndianFromFile(file, &key)) 654 return false; 655 656 if (key != kCompressedKey) 657 return false; 658 659 int content_width = 0; 660 if (!ReadBigEndianFromFile(file, &content_width) || content_width <= 0) 661 return false; 662 663 int content_height = 0; 664 if (!ReadBigEndianFromFile(file, &content_height) || content_height <= 0) 665 return false; 666 667 out_content_size->SetSize(content_width, content_height); 668 669 // Read ETC1 header. 670 int header_bytes_read = 0; 671 unsigned char etc1_buffer[ETC_PKM_HEADER_SIZE]; 672 header_bytes_read = file.ReadAtCurrentPos( 673 reinterpret_cast<char*>(etc1_buffer), 674 ETC_PKM_HEADER_SIZE); 675 if (header_bytes_read != ETC_PKM_HEADER_SIZE) 676 return false; 677 678 if (!etc1_pkm_is_valid(etc1_buffer)) 679 return false; 680 681 int raw_width = 0; 682 raw_width = etc1_pkm_get_width(etc1_buffer); 683 if (raw_width <= 0) 684 return false; 685 686 int raw_height = 0; 687 raw_height = etc1_pkm_get_height(etc1_buffer); 688 if (raw_height <= 0) 689 return false; 690 691 // Do some simple sanity check validation. We can't have thumbnails larger 692 // than the max display size of the screen. We also can't have etc1 texture 693 // data larger than the next power of 2 up from that. 694 gfx::DeviceDisplayInfo display_info; 695 int max_dimension = std::max(display_info.GetDisplayWidth(), 696 display_info.GetDisplayHeight()); 697 698 if (content_width > max_dimension 699 || content_height > max_dimension 700 || static_cast<size_t>(raw_width) > NextPowerOfTwo(max_dimension) 701 || static_cast<size_t>(raw_height) > NextPowerOfTwo(max_dimension)) { 702 return false; 703 } 704 705 int data_size = etc1_get_encoded_data_size(raw_width, raw_height); 706 skia::RefPtr<SkData> etc1_pixel_data = 707 skia::AdoptRef(SkData::NewUninitialized(data_size)); 708 709 int pixel_bytes_read = file.ReadAtCurrentPos( 710 reinterpret_cast<char*>(etc1_pixel_data->writable_data()), 711 data_size); 712 713 if (pixel_bytes_read != data_size) 714 return false; 715 716 SkImageInfo info = SkImageInfo::Make(raw_width, 717 raw_height, 718 kUnknown_SkColorType, 719 kUnpremul_SkAlphaType); 720 721 *out_pixels = skia::AdoptRef( 722 SkMallocPixelRef::NewWithData(info, 723 0, 724 NULL, 725 etc1_pixel_data.get())); 726 727 int extra_data_version = 0; 728 if (!ReadBigEndianFromFile(file, &extra_data_version)) 729 return false; 730 731 *out_scale = 1.f; 732 if (extra_data_version == 1) { 733 if (!ReadBigEndianFloatFromFile(file, out_scale)) 734 return false; 735 736 if (*out_scale == 0.f) 737 return false; 738 739 *out_scale = 1.f / *out_scale; 740 } 741 742 return true; 743 } 744 745 }// anonymous namespace 746 747 void ThumbnailStore::ReadTask( 748 bool decompress, 749 const base::FilePath& file_path, 750 const base::Callback< 751 void(skia::RefPtr<SkPixelRef>, float, const gfx::Size&)>& 752 post_read_task) { 753 gfx::Size content_size; 754 float scale = 0.f; 755 skia::RefPtr<SkPixelRef> compressed_data; 756 757 if (base::PathExists(file_path)) { 758 base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ); 759 760 761 bool valid_contents = ReadFromFile(file, 762 &content_size, 763 &scale, 764 &compressed_data); 765 file.Close(); 766 767 if (!valid_contents) { 768 content_size.SetSize(0, 0); 769 scale = 0.f; 770 compressed_data.clear(); 771 base::DeleteFile(file_path, false); 772 } 773 } 774 775 if (decompress) { 776 base::WorkerPool::PostTask( 777 FROM_HERE, 778 base::Bind(post_read_task, compressed_data, scale, content_size), 779 true); 780 } else { 781 content::BrowserThread::PostTask( 782 content::BrowserThread::UI, 783 FROM_HERE, 784 base::Bind(post_read_task, compressed_data, scale, content_size)); 785 } 786 } 787 788 void ThumbnailStore::PostReadTask(TabId tab_id, 789 skia::RefPtr<SkPixelRef> compressed_data, 790 float scale, 791 const gfx::Size& content_size) { 792 read_in_progress_ = false; 793 794 TabIdList::iterator iter = 795 std::find(read_queue_.begin(), read_queue_.end(), tab_id); 796 if (iter == read_queue_.end()) { 797 ReadNextThumbnail(); 798 return; 799 } 800 801 read_queue_.erase(iter); 802 803 if (!cache_.Get(tab_id) && compressed_data) { 804 ThumbnailMetaDataMap::iterator meta_iter = 805 thumbnail_meta_data_.find(tab_id); 806 base::Time time_stamp = base::Time::Now(); 807 if (meta_iter != thumbnail_meta_data_.end()) 808 time_stamp = meta_iter->second.capture_time(); 809 810 MakeSpaceForNewItemIfNecessary(tab_id); 811 scoped_ptr<Thumbnail> thumbnail = Thumbnail::Create( 812 tab_id, time_stamp, scale, ui_resource_provider_, this); 813 thumbnail->SetCompressedBitmap(compressed_data, 814 content_size); 815 if (kPreferCPUMemory) 816 thumbnail->CreateUIResource(); 817 818 cache_.Put(tab_id, thumbnail.Pass()); 819 NotifyObserversOfThumbnailRead(tab_id); 820 } 821 822 ReadNextThumbnail(); 823 } 824 825 void ThumbnailStore::NotifyObserversOfThumbnailRead(TabId tab_id) { 826 FOR_EACH_OBSERVER( 827 ThumbnailStoreObserver, observers_, OnFinishedThumbnailRead(tab_id)); 828 } 829 830 void ThumbnailStore::RemoveOnMatchedTimeStamp(TabId tab_id, 831 const base::Time& time_stamp) { 832 // We remove the cached version if it matches the tab_id and the time_stamp. 833 Thumbnail* thumbnail = cache_.Get(tab_id); 834 Thumbnail* approx_thumbnail = approximation_cache_.Get(tab_id); 835 if ((thumbnail && thumbnail->time_stamp() == time_stamp) || 836 (approx_thumbnail && approx_thumbnail->time_stamp() == time_stamp)) { 837 Remove(tab_id); 838 } 839 return; 840 } 841 842 void ThumbnailStore::DecompressionTask( 843 const base::Callback<void(bool, SkBitmap)>& 844 post_decompression_callback, 845 skia::RefPtr<SkPixelRef> compressed_data, 846 float scale, 847 const gfx::Size& content_size) { 848 SkBitmap raw_data_small; 849 bool success = false; 850 851 if (compressed_data.get()) { 852 gfx::Size buffer_size = gfx::Size(compressed_data->info().width(), 853 compressed_data->info().height()); 854 855 SkBitmap raw_data; 856 raw_data.allocPixels(SkImageInfo::Make(buffer_size.width(), 857 buffer_size.height(), 858 kRGBA_8888_SkColorType, 859 kOpaque_SkAlphaType)); 860 SkAutoLockPixels raw_data_lock(raw_data); 861 compressed_data->lockPixels(); 862 success = etc1_decode_image( 863 reinterpret_cast<unsigned char*>(compressed_data->pixels()), 864 reinterpret_cast<unsigned char*>(raw_data.getPixels()), 865 buffer_size.width(), 866 buffer_size.height(), 867 raw_data.bytesPerPixel(), 868 raw_data.rowBytes()); 869 compressed_data->unlockPixels(); 870 raw_data.setImmutable(); 871 872 if (!success) { 873 // Leave raw_data_small empty for consistency with other failure modes. 874 } else if (content_size == buffer_size) { 875 // Shallow copy the pixel reference. 876 raw_data_small = raw_data; 877 } else { 878 // The content size is smaller than the buffer size (likely because of 879 // a power-of-two rounding), so deep copy the bitmap. 880 raw_data_small.allocPixels(SkImageInfo::Make(content_size.width(), 881 content_size.height(), 882 kRGBA_8888_SkColorType, 883 kOpaque_SkAlphaType)); 884 SkAutoLockPixels raw_data_small_lock(raw_data_small); 885 SkCanvas small_canvas(raw_data_small); 886 small_canvas.drawBitmap(raw_data, 0, 0); 887 raw_data_small.setImmutable(); 888 } 889 } 890 891 content::BrowserThread::PostTask( 892 content::BrowserThread::UI, 893 FROM_HERE, 894 base::Bind(post_decompression_callback, success, raw_data_small)); 895 } 896 897 ThumbnailStore::ThumbnailMetaData::ThumbnailMetaData() { 898 } 899 900 ThumbnailStore::ThumbnailMetaData::ThumbnailMetaData( 901 const base::Time& current_time, 902 const GURL& url) 903 : capture_time_(current_time), url_(url) { 904 } 905 906 std::pair<SkBitmap, float> ThumbnailStore::CreateApproximation( 907 const SkBitmap& bitmap, 908 float scale) { 909 DCHECK(!bitmap.empty()); 910 DCHECK_GT(scale, 0); 911 SkAutoLockPixels bitmap_lock(bitmap); 912 float new_scale = 1.f / kApproximationScaleFactor; 913 914 gfx::Size dst_size = gfx::ToFlooredSize( 915 gfx::ScaleSize(gfx::Size(bitmap.width(), bitmap.height()), new_scale)); 916 SkBitmap dst_bitmap; 917 dst_bitmap.allocPixels(SkImageInfo::Make(dst_size.width(), 918 dst_size.height(), 919 bitmap.info().fColorType, 920 bitmap.info().fAlphaType)); 921 dst_bitmap.eraseColor(0); 922 SkAutoLockPixels dst_bitmap_lock(dst_bitmap); 923 924 SkCanvas canvas(dst_bitmap); 925 canvas.scale(new_scale, new_scale); 926 canvas.drawBitmap(bitmap, 0, 0, NULL); 927 dst_bitmap.setImmutable(); 928 929 return std::make_pair(dst_bitmap, new_scale * scale); 930 } 931