root/chrome/browser/thumbnails/content_based_thumbnailing_algorithm.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. CallbackInvocationAdapter
  2. GetCanvasCopyInfo
  3. ProcessBitmap
  4. PrepareSourceBitmap
  5. CreateRetargetedThumbnail
  6. GetClippingRect

// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/thumbnails/content_based_thumbnailing_algorithm.h"

#include "base/metrics/histogram.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/thumbnails/content_analysis.h"
#include "chrome/browser/thumbnails/simple_thumbnail_crop.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/scrollbar_size.h"
#include "ui/gfx/size_conversions.h"
#include "ui/gfx/skbitmap_operations.h"
#include "ui/gfx/skia_util.h"

namespace {

const char kThumbnailHistogramName[] = "Thumbnail.RetargetMS";
const char kFailureHistogramName[] = "Thumbnail.FailedRetargetMS";
const float kScoreBoostFromSuccessfulRetargeting = 1.1f;

void CallbackInvocationAdapter(
    const thumbnails::ThumbnailingAlgorithm::ConsumerCallback& callback,
    scoped_refptr<thumbnails::ThumbnailingContext> context,
    const SkBitmap& source_bitmap) {
  callback.Run(*context.get(), source_bitmap);
}

}  // namespace

namespace thumbnails {

using content::BrowserThread;

ContentBasedThumbnailingAlgorithm::ContentBasedThumbnailingAlgorithm(
    const gfx::Size& target_size)
    : target_size_(target_size) {
  DCHECK(!target_size.IsEmpty());
}

ClipResult ContentBasedThumbnailingAlgorithm::GetCanvasCopyInfo(
    const gfx::Size& source_size,
    ui::ScaleFactor scale_factor,
    gfx::Rect* clipping_rect,
    gfx::Size* target_size) const {
  DCHECK(!source_size.IsEmpty());
  gfx::Size target_thumbnail_size =
      SimpleThumbnailCrop::GetCopySizeForThumbnail(scale_factor, target_size_);

  ClipResult clipping_method = thumbnails::CLIP_RESULT_NOT_CLIPPED;
  *clipping_rect = GetClippingRect(
      source_size, target_thumbnail_size, target_size, &clipping_method);
  return clipping_method;
}

void ContentBasedThumbnailingAlgorithm::ProcessBitmap(
    scoped_refptr<ThumbnailingContext> context,
    const ConsumerCallback& callback,
    const SkBitmap& bitmap) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(context.get());
  if (bitmap.isNull() || bitmap.empty())
    return;

  gfx::Size target_thumbnail_size =
      SimpleThumbnailCrop::ComputeTargetSizeAtMaximumScale(target_size_);

  SkBitmap source_bitmap =
      PrepareSourceBitmap(bitmap, target_thumbnail_size, context.get());

  // If the source is same (or smaller) than the target, just return it as
  // the final result. Otherwise, send the shrinking task to the blocking
  // thread pool.
  if (source_bitmap.width() <= target_thumbnail_size.width() ||
      source_bitmap.height() <= target_thumbnail_size.height()) {
    context->score.boring_score =
        SimpleThumbnailCrop::CalculateBoringScore(source_bitmap);
    context->score.good_clipping =
        (context->clip_result == CLIP_RESULT_WIDER_THAN_TALL ||
         context->clip_result == CLIP_RESULT_TALLER_THAN_WIDE ||
         context->clip_result == CLIP_RESULT_NOT_CLIPPED ||
         context->clip_result == CLIP_RESULT_SOURCE_SAME_AS_TARGET);

    callback.Run(*context.get(), source_bitmap);
    return;
  }

  if (!BrowserThread::GetBlockingPool()->PostWorkerTaskWithShutdownBehavior(
          FROM_HERE,
          base::Bind(&CreateRetargetedThumbnail,
                     source_bitmap,
                     target_thumbnail_size,
                     context,
                     callback),
          base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)) {
    LOG(WARNING) << "PostSequencedWorkerTask failed. The thumbnail for "
                 << context->url << " will not be created.";
  }
}

// static
SkBitmap ContentBasedThumbnailingAlgorithm::PrepareSourceBitmap(
    const SkBitmap& received_bitmap,
    const gfx::Size& thumbnail_size,
    ThumbnailingContext* context) {
  gfx::Size resize_target;
  SkBitmap clipped_bitmap;
  if (context->clip_result == CLIP_RESULT_UNPROCESSED) {
    // This case will require extracting a fragment from the retrieved bitmap.
    int scrollbar_size = gfx::scrollbar_size();
    gfx::Size scrollbarless(
        std::max(1, received_bitmap.width() - scrollbar_size),
        std::max(1, received_bitmap.height() - scrollbar_size));

    gfx::Rect clipping_rect = GetClippingRect(
        scrollbarless,
        thumbnail_size,
        &resize_target,
        &context->clip_result);

    received_bitmap.extractSubset(&clipped_bitmap,
                                  gfx::RectToSkIRect(clipping_rect));
  } else {
    // This means that the source bitmap has been requested and at least
    // clipped. Upstream code in same cases seems opportunistic and it may
    // not perform actual resizing if copying with resize is not supported.
    // In this case we will resize to the orignally requested copy size.
    resize_target = context->requested_copy_size;
    clipped_bitmap = received_bitmap;
  }

  SkBitmap result_bitmap = SkBitmapOperations::DownsampleByTwoUntilSize(
      clipped_bitmap, resize_target.width(), resize_target.height());
#if !defined(USE_AURA)
  // If the bitmap has not been indeed resized, it has to be copied. In that
  // case resampler simply returns a reference to the original bitmap, sitting
  // in PlatformCanvas. One does not simply assign these 'magic' bitmaps to
  // SkBitmap. They cannot be refcounted.
  //
  // With Aura, this does not happen since PlatformCanvas is platform
  // idependent.
  if (clipped_bitmap.width() == result_bitmap.width() &&
      clipped_bitmap.height() == result_bitmap.height()) {
    clipped_bitmap.copyTo(&result_bitmap, kPMColor_SkColorType);
  }
#endif

  return result_bitmap;
}

// static
void ContentBasedThumbnailingAlgorithm::CreateRetargetedThumbnail(
    const SkBitmap& source_bitmap,
    const gfx::Size& thumbnail_size,
    scoped_refptr<ThumbnailingContext> context,
    const ConsumerCallback& callback) {
  base::TimeTicks begin_compute_thumbnail = base::TimeTicks::Now();
  float kernel_sigma =
      context->clip_result == CLIP_RESULT_SOURCE_SAME_AS_TARGET ? 5.0f : 2.5f;
  SkBitmap thumbnail = thumbnailing_utils::CreateRetargetedThumbnailImage(
      source_bitmap, thumbnail_size, kernel_sigma);
  bool processing_failed = thumbnail.empty();
  if (processing_failed) {
    // Log and apply the method very much like in SimpleThumbnailCrop (except
    // that some clipping and copying is not required).
    LOG(WARNING) << "CreateRetargetedThumbnailImage failed. "
                 << "The thumbnail for " << context->url
                 << " will be created the old-fashioned way.";

    ClipResult clip_result;
    gfx::Rect clipping_rect = SimpleThumbnailCrop::GetClippingRect(
        gfx::Size(source_bitmap.width(), source_bitmap.height()),
        thumbnail_size,
        &clip_result);
    source_bitmap.extractSubset(&thumbnail, gfx::RectToSkIRect(clipping_rect));
    thumbnail = SkBitmapOperations::DownsampleByTwoUntilSize(
        thumbnail, thumbnail_size.width(), thumbnail_size.height());
  }

  HISTOGRAM_TIMES(
      processing_failed ? kFailureHistogramName : kThumbnailHistogramName,
      base::TimeTicks::Now() - begin_compute_thumbnail);
  context->score.boring_score =
        SimpleThumbnailCrop::CalculateBoringScore(source_bitmap);
  if (!processing_failed)
    context->score.boring_score *= kScoreBoostFromSuccessfulRetargeting;
  context->score.good_clipping =
      (context->clip_result == CLIP_RESULT_WIDER_THAN_TALL ||
       context->clip_result == CLIP_RESULT_TALLER_THAN_WIDE ||
       context->clip_result == CLIP_RESULT_NOT_CLIPPED ||
       context->clip_result == CLIP_RESULT_SOURCE_SAME_AS_TARGET);
  // Post the result (the bitmap) back to the callback.
  BrowserThread::PostTask(
      BrowserThread::UI,
      FROM_HERE,
      base::Bind(&CallbackInvocationAdapter, callback, context, thumbnail));
}

ContentBasedThumbnailingAlgorithm::~ContentBasedThumbnailingAlgorithm() {
}

//  static
gfx::Rect ContentBasedThumbnailingAlgorithm::GetClippingRect(
    const gfx::Size& source_size,
    const gfx::Size& thumbnail_size,
    gfx::Size* target_size,
    ClipResult* clip_result) {
  // Compute and return the clipping rectagle of the source image and the
  // size of the target bitmap which will be used for the further processing.
  // This function in 'general case' is trivial (don't clip, halve the source)
  // but it is needed for handling edge cases (source smaller than the target
  // thumbnail size).
  DCHECK(target_size);
  DCHECK(clip_result);
  gfx::Rect clipping_rect;
  if (source_size.width() < thumbnail_size.width() ||
      source_size.height() < thumbnail_size.height()) {
    clipping_rect = gfx::Rect(thumbnail_size);
    *target_size = thumbnail_size;
    *clip_result = CLIP_RESULT_SOURCE_IS_SMALLER;
  } else if (source_size.width() < thumbnail_size.width() * 4 ||
             source_size.height() < thumbnail_size.height() * 4) {
    clipping_rect = gfx::Rect(source_size);
    *target_size = source_size;
    *clip_result = CLIP_RESULT_SOURCE_SAME_AS_TARGET;
  } else {
    clipping_rect = gfx::Rect(source_size);
    target_size->SetSize(source_size.width() / 2, source_size.height() / 2);
    *clip_result = CLIP_RESULT_NOT_CLIPPED;
  }

  return clipping_rect;
}

}  // namespace thumbnails

/* [<][>][^][v][top][bottom][index][help] */