blob: 8a15a24519e8c31c8c842b4d87e9bfa3f2f97608 [file] [log] [blame]
/*
* Copyright (C) 2007 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/platform/drag_image.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_refptr.h"
#include "skia/ext/image_operations.h"
#include "third_party/blink/renderer/platform/fonts/font.h"
#include "third_party/blink/renderer/platform/fonts/font_cache.h"
#include "third_party/blink/renderer/platform/fonts/font_description.h"
#include "third_party/blink/renderer/platform/fonts/font_metrics.h"
#include "third_party/blink/renderer/platform/fonts/string_truncator.h"
#include "third_party/blink/renderer/platform/fonts/text_run_paint_info.h"
#include "third_party/blink/renderer/platform/geometry/float_point.h"
#include "third_party/blink/renderer/platform/geometry/float_rect.h"
#include "third_party/blink/renderer/platform/geometry/int_point.h"
#include "third_party/blink/renderer/platform/graphics/bitmap_image.h"
#include "third_party/blink/renderer/platform/graphics/canvas_resource_provider.h"
#include "third_party/blink/renderer/platform/graphics/color.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
#include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
#include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/text/bidi_text_run.h"
#include "third_party/blink/renderer/platform/text/text_run.h"
#include "third_party/blink/renderer/platform/transforms/affine_transform.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColorSpaceXformCanvas.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkMatrix.h"
#include "third_party/skia/include/core/SkSurface.h"
namespace blink {
namespace {
const float kDragLabelBorderX = 4;
// Keep border_y in synch with DragController::LinkDragBorderInset.
const float kDragLabelBorderY = 2;
const float kLabelBorderYOffset = 2;
const float kMaxDragLabelWidth = 300;
const float kMaxDragLabelStringWidth =
(kMaxDragLabelWidth - 2 * kDragLabelBorderX);
const float kDragLinkLabelFontSize = 11;
const float kDragLinkUrlFontSize = 10;
} // anonymous namespace
PaintImage DragImage::ResizeAndOrientImage(
const PaintImage& image,
ImageOrientation orientation,
FloatSize image_scale,
float opacity,
InterpolationQuality interpolation_quality) {
IntSize size(image.width(), image.height());
size.Scale(image_scale.Width(), image_scale.Height());
AffineTransform transform;
if (orientation != kDefaultImageOrientation) {
if (orientation.UsesWidthAsHeight())
size = size.TransposedSize();
transform *= orientation.TransformFromDefault(FloatSize(size));
}
transform.ScaleNonUniform(image_scale.Width(), image_scale.Height());
if (size.IsEmpty())
return PaintImage();
if (transform.IsIdentity() && opacity == 1) {
// Nothing to adjust, just use the original.
DCHECK_EQ(image.width(), size.Width());
DCHECK_EQ(image.height(), size.Height());
return image;
}
sk_sp<SkSurface> surface =
SkSurface::MakeRasterN32Premul(size.Width(), size.Height());
if (!surface)
return PaintImage();
SkPaint paint;
DCHECK_GE(opacity, 0);
DCHECK_LE(opacity, 1);
paint.setAlpha(opacity * 255);
paint.setFilterQuality(interpolation_quality == kInterpolationNone
? kNone_SkFilterQuality
: kHigh_SkFilterQuality);
SkCanvas* canvas = surface->getCanvas();
std::unique_ptr<SkCanvas> color_transform_canvas;
color_transform_canvas =
SkCreateColorSpaceXformCanvas(canvas, SkColorSpace::MakeSRGB());
canvas = color_transform_canvas.get();
canvas->concat(AffineTransformToSkMatrix(transform));
canvas->drawImage(image.GetSkImage(), 0, 0, &paint);
return PaintImageBuilder::WithProperties(std::move(image))
.set_image(surface->makeImageSnapshot(), PaintImage::GetNextContentId())
.TakePaintImage();
}
FloatSize DragImage::ClampedImageScale(const IntSize& image_size,
const IntSize& size,
const IntSize& max_size) {
// Non-uniform scaling for size mapping.
FloatSize image_scale(
static_cast<float>(size.Width()) / image_size.Width(),
static_cast<float>(size.Height()) / image_size.Height());
// Uniform scaling for clamping.
const float clamp_scale_x =
size.Width() > max_size.Width()
? static_cast<float>(max_size.Width()) / size.Width()
: 1;
const float clamp_scale_y =
size.Height() > max_size.Height()
? static_cast<float>(max_size.Height()) / size.Height()
: 1;
image_scale.Scale(std::min(clamp_scale_x, clamp_scale_y));
return image_scale;
}
std::unique_ptr<DragImage> DragImage::Create(
Image* image,
RespectImageOrientationEnum should_respect_image_orientation,
float device_scale_factor,
InterpolationQuality interpolation_quality,
float opacity,
FloatSize image_scale) {
if (!image)
return nullptr;
PaintImage paint_image = image->PaintImageForCurrentFrame();
if (!paint_image)
return nullptr;
ImageOrientation orientation;
if (should_respect_image_orientation == kRespectImageOrientation &&
image->IsBitmapImage())
orientation = ToBitmapImage(image)->CurrentFrameOrientation();
SkBitmap bm;
paint_image = ResizeAndOrientImage(paint_image, orientation, image_scale,
opacity, interpolation_quality);
if (!paint_image || !paint_image.GetSkImage()->asLegacyBitmap(&bm))
return nullptr;
return base::WrapUnique(
new DragImage(bm, device_scale_factor, interpolation_quality));
}
static Font DeriveDragLabelFont(int size,
FontSelectionValue font_weight,
const FontDescription& system_font) {
FontDescription description = system_font;
description.SetWeight(font_weight);
description.SetSpecifiedSize(size);
description.SetComputedSize(size);
Font result(description);
result.Update(nullptr);
return result;
}
std::unique_ptr<DragImage> DragImage::Create(const KURL& url,
const String& in_label,
const FontDescription& system_font,
float device_scale_factor) {
const Font label_font = DeriveDragLabelFont(kDragLinkLabelFontSize,
BoldWeightValue(), system_font);
const SimpleFontData* label_font_data = label_font.PrimaryFont();
DCHECK(label_font_data);
const Font url_font = DeriveDragLabelFont(kDragLinkUrlFontSize,
NormalWeightValue(), system_font);
const SimpleFontData* url_font_data = url_font.PrimaryFont();
DCHECK(url_font_data);
if (!label_font_data || !url_font_data)
return nullptr;
FontCachePurgePreventer font_cache_purge_preventer;
bool draw_url_string = true;
bool clip_url_string = false;
bool clip_label_string = false;
float max_drag_label_string_width_dip =
kMaxDragLabelStringWidth / device_scale_factor;
String url_string = url.GetString();
String label = in_label.StripWhiteSpace();
if (label.IsEmpty()) {
draw_url_string = false;
label = url_string;
}
// First step is drawing the link drag image width.
TextRun label_run(label.Impl());
TextRun url_run(url_string.Impl());
IntSize label_size(label_font.Width(label_run),
label_font_data->GetFontMetrics().Ascent() +
label_font_data->GetFontMetrics().Descent());
if (label_size.Width() > max_drag_label_string_width_dip) {
label_size.SetWidth(max_drag_label_string_width_dip);
clip_label_string = true;
}
IntSize url_string_size;
IntSize image_size(label_size.Width() + kDragLabelBorderX * 2,
label_size.Height() + kDragLabelBorderY * 2);
if (draw_url_string) {
url_string_size.SetWidth(url_font.Width(url_run));
url_string_size.SetHeight(url_font_data->GetFontMetrics().Ascent() +
url_font_data->GetFontMetrics().Descent());
image_size.SetHeight(image_size.Height() + url_string_size.Height());
if (url_string_size.Width() > max_drag_label_string_width_dip) {
image_size.SetWidth(max_drag_label_string_width_dip);
clip_url_string = true;
} else {
image_size.SetWidth(
std::max(label_size.Width(), url_string_size.Width()) +
kDragLabelBorderX * 2);
}
}
// We now know how big the image needs to be, so we create and
// fill the background
IntSize scaled_image_size = image_size;
scaled_image_size.Scale(device_scale_factor);
std::unique_ptr<CanvasResourceProvider> resource_provider(
CanvasResourceProvider::Create(
scaled_image_size, CanvasResourceProvider::kSoftwareResourceUsage,
nullptr, // context_provider_wrapper
0, // msaa_sample_count
CanvasColorParams(), CanvasResourceProvider::kDefaultPresentationMode,
nullptr)); // canvas_resource_dispatcher
if (!resource_provider)
return nullptr;
resource_provider->Canvas()->scale(device_scale_factor, device_scale_factor);
const float kDragLabelRadius = 5;
IntRect rect(IntPoint(), image_size);
PaintFlags background_paint;
background_paint.setColor(SkColorSetRGB(140, 140, 140));
background_paint.setAntiAlias(true);
SkRRect rrect;
rrect.setRectXY(SkRect::MakeWH(image_size.Width(), image_size.Height()),
kDragLabelRadius, kDragLabelRadius);
resource_provider->Canvas()->drawRRect(rrect, background_paint);
// Draw the text
PaintFlags text_paint;
if (draw_url_string) {
if (clip_url_string)
url_string = StringTruncator::CenterTruncate(
url_string, image_size.Width() - (kDragLabelBorderX * 2.0f),
url_font);
FloatPoint text_pos(
kDragLabelBorderX,
image_size.Height() -
(kLabelBorderYOffset + url_font_data->GetFontMetrics().Descent()));
TextRun text_run(url_string);
url_font.DrawText(resource_provider->Canvas(), TextRunPaintInfo(text_run),
text_pos, device_scale_factor, text_paint);
}
if (clip_label_string)
label = StringTruncator::RightTruncate(
label, image_size.Width() - (kDragLabelBorderX * 2.0f), label_font);
bool has_strong_directionality;
TextRun text_run =
TextRunWithDirectionality(label, &has_strong_directionality);
IntPoint text_pos(
kDragLabelBorderX,
kDragLabelBorderY + label_font.GetFontDescription().ComputedPixelSize());
if (has_strong_directionality &&
text_run.Direction() == TextDirection::kRtl) {
float text_width = label_font.Width(text_run);
int available_width = image_size.Width() - kDragLabelBorderX * 2;
text_pos.SetX(available_width - ceilf(text_width));
}
label_font.DrawBidiText(resource_provider->Canvas(),
TextRunPaintInfo(text_run), FloatPoint(text_pos),
Font::kDoNotPaintIfFontNotReady, device_scale_factor,
text_paint);
scoped_refptr<StaticBitmapImage> image = resource_provider->Snapshot();
return DragImage::Create(image.get(), kDoNotRespectImageOrientation,
device_scale_factor);
}
DragImage::DragImage(const SkBitmap& bitmap,
float resolution_scale,
InterpolationQuality interpolation_quality)
: bitmap_(bitmap),
resolution_scale_(resolution_scale),
interpolation_quality_(interpolation_quality) {}
DragImage::~DragImage() = default;
void DragImage::Scale(float scale_x, float scale_y) {
skia::ImageOperations::ResizeMethod resize_method =
interpolation_quality_ == kInterpolationNone
? skia::ImageOperations::RESIZE_BOX
: skia::ImageOperations::RESIZE_LANCZOS3;
int image_width = scale_x * bitmap_.width();
int image_height = scale_y * bitmap_.height();
bitmap_ = skia::ImageOperations::Resize(bitmap_, resize_method, image_width,
image_height);
}
} // namespace blink