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
29namespace {
30
31const float kApproximationScaleFactor = 4.f;
32const base::TimeDelta kCaptureMinRequestTimeMs(
33    base::TimeDelta::FromMilliseconds(1000));
34
35const int kCompressedKey = 0xABABABAB;
36const int kCurrentExtraVersion = 1;
37
38// Indicates whether we prefer to have more free CPU memory over GPU memory.
39const bool kPreferCPUMemory = true;
40
41size_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
51size_t RoundUpMod4(size_t x) {
52  return (x + 3) & ~3;
53}
54
55gfx::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
65template<typename T>
66bool 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
74template<typename T>
75bool 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
81bool 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
98bool 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
114ThumbnailStore::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
134ThumbnailStore::~ThumbnailStore() {
135  SetUIResourceProvider(NULL);
136}
137
138void 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
149void ThumbnailStore::AddThumbnailStoreObserver(
150    ThumbnailStoreObserver* observer) {
151  if (!observers_.HasObserver(observer))
152    observers_.AddObserver(observer);
153}
154
155void ThumbnailStore::RemoveThumbnailStoreObserver(
156    ThumbnailStoreObserver* observer) {
157  if (observers_.HasObserver(observer))
158    observers_.RemoveObserver(observer);
159}
160
161void 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
189void 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
197Thumbnail* 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
226void 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
235void 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
246bool 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
262void 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
308void 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
324void 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
332void ThumbnailStore::RemoveFromDiskTask(const base::FilePath& file_path) {
333  if (base::PathExists(file_path))
334    base::DeleteFile(file_path, false);
335}
336
337void 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
353void 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
375void 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
406void 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
425void 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
465void 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
472void 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
486base::FilePath ThumbnailStore::GetFilePath(TabId tab_id) const {
487  return disk_cache_path_.Append(base::IntToString(tab_id));
488}
489
490namespace {
491
492bool 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
543void 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
567void ThumbnailStore::PostWriteTask() {
568  write_tasks_count_--;
569}
570
571void 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
621void 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
643namespace {
644
645bool 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
747void 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
788void 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
825void ThumbnailStore::NotifyObserversOfThumbnailRead(TabId tab_id) {
826  FOR_EACH_OBSERVER(
827      ThumbnailStoreObserver, observers_, OnFinishedThumbnailRead(tab_id));
828}
829
830void 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
842void 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
897ThumbnailStore::ThumbnailMetaData::ThumbnailMetaData() {
898}
899
900ThumbnailStore::ThumbnailMetaData::ThumbnailMetaData(
901    const base::Time& current_time,
902    const GURL& url)
903    : capture_time_(current_time), url_(url) {
904}
905
906std::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