blob: dba1d08c8faec8f31e9a8ec9831fe210cf0b6df3 [file] [log] [blame]
/*
* Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
* Copyright (C) 2004, 2005, 2006 Apple Computer, 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/graphics/image.h"
#include "base/numerics/checked_math.h"
#include "build/build_config.h"
#include "cc/tiles/software_image_decode_cache.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/web_data.h"
#include "third_party/blink/renderer/platform/drag_image.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/float_size.h"
#include "third_party/blink/renderer/platform/geometry/length.h"
#include "third_party/blink/renderer/platform/graphics/bitmap_image.h"
#include "third_party/blink/renderer/platform/graphics/deferred_image_decoder.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_image.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_recorder.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_shader.h"
#include "third_party/blink/renderer/platform/graphics/scoped_interpolation_quality.h"
#include "third_party/blink/renderer/platform/histogram.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/shared_buffer.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/skia/include/core/SkImage.h"
#include <math.h>
#include <tuple>
namespace blink {
class CombinedImageDecodeCache {
public:
CombinedImageDecodeCache(size_t locked_memory_limit_bytes)
: locked_memory_limit_bytes_(locked_memory_limit_bytes) {
constexpr int kMaxIndex =
(kMaxCanvasPixelFormat + 1) * (kMaxCanvasColorSpace + 1);
decode_caches_.resize(kMaxIndex);
}
cc::ImageDecodeCache* GetCache(CanvasColorSpace color_space,
CanvasPixelFormat pixel_format) {
base::AutoLock lock(lock_);
int index = (kMaxCanvasColorSpace + 1) * pixel_format + color_space;
if (!decode_caches_[index]) {
decode_caches_[index] = std::make_unique<cc::SoftwareImageDecodeCache>(
CanvasColorParams::PixelFormatToSkColorType(pixel_format),
locked_memory_limit_bytes_, PaintImage::kDefaultGeneratorClientId,
blink::CanvasColorParams::CanvasColorSpaceToSkColorSpace(
color_space));
}
return decode_caches_[index].get();
}
private:
std::vector<std::unique_ptr<cc::SoftwareImageDecodeCache>> decode_caches_;
const size_t locked_memory_limit_bytes_;
base::Lock lock_;
};
Image::Image(ImageObserver* observer, bool is_multipart)
: image_observer_disabled_(false),
image_observer_(observer),
stable_image_id_(PaintImage::GetNextId()),
is_multipart_(is_multipart),
high_contrast_classification_(
HighContrastClassification::kNotClassified) {}
Image::~Image() = default;
Image* Image::NullImage() {
DCHECK(IsMainThread());
DEFINE_STATIC_REF(Image, null_image, (BitmapImage::Create()));
return null_image;
}
// static
cc::ImageDecodeCache* Image::SharedCCDecodeCache(
CanvasColorSpace color_space,
CanvasPixelFormat pixel_format) {
// This denotes the allocated locked memory budget for the cache used for
// book-keeping. The cache indicates when the total memory locked exceeds this
// budget in cc::DecodedDrawImage.
static const size_t kLockedMemoryLimitBytes = 64 * 1024 * 1024;
DEFINE_THREAD_SAFE_STATIC_LOCAL(CombinedImageDecodeCache, combined_cache,
(kLockedMemoryLimitBytes));
return combined_cache.GetCache(color_space, pixel_format);
}
scoped_refptr<Image> Image::LoadPlatformResource(const char* name) {
const WebData& resource = Platform::Current()->GetDataResource(name);
if (resource.IsEmpty())
return Image::NullImage();
scoped_refptr<Image> image = BitmapImage::Create();
image->SetData(resource, true);
return image;
}
Image::SizeAvailability Image::SetData(scoped_refptr<SharedBuffer> data,
bool all_data_received) {
encoded_image_data_ = std::move(data);
if (!encoded_image_data_.get())
return kSizeAvailable;
int length = encoded_image_data_->size();
if (!length)
return kSizeAvailable;
return DataChanged(all_data_received);
}
String Image::FilenameExtension() const {
return String();
}
// TODO(schenney): Lift this code, with the calculations for subsetting the
// image and the like, up the stack into a border painting class.
void Image::DrawTiledBorder(GraphicsContext& ctxt,
const FloatRect& dst_rect,
const FloatRect& src_rect,
const FloatSize& provided_tile_scale_factor,
TileRule h_rule,
TileRule v_rule,
SkBlendMode op) {
// TODO(cavalcantii): see crbug.com/662513.
FloatSize tile_scale_factor = provided_tile_scale_factor;
if (v_rule == kRoundTile) {
float v_repetitions = std::max(
1.0f, roundf(dst_rect.Height() /
(tile_scale_factor.Height() * src_rect.Height())));
tile_scale_factor.SetHeight(dst_rect.Height() /
(src_rect.Height() * v_repetitions));
}
if (h_rule == kRoundTile) {
float h_repetitions =
std::max(1.0f, roundf(dst_rect.Width() /
(tile_scale_factor.Width() * src_rect.Width())));
tile_scale_factor.SetWidth(dst_rect.Width() /
(src_rect.Width() * h_repetitions));
}
// We want to construct the phase such that the pattern is centered (when
// stretch is not set for a particular rule).
float v_phase = tile_scale_factor.Height() * src_rect.Y();
float h_phase = tile_scale_factor.Width() * src_rect.X();
if (v_rule == kRepeatTile) {
float scaled_tile_height = tile_scale_factor.Height() * src_rect.Height();
v_phase -= (dst_rect.Height() - scaled_tile_height) / 2;
}
if (h_rule == kRepeatTile) {
float scaled_tile_width = tile_scale_factor.Width() * src_rect.Width();
h_phase -= (dst_rect.Width() - scaled_tile_width) / 2;
}
FloatSize spacing;
auto calculate_space_needed =
[](const float destination,
const float source) -> std::tuple<bool, float> {
DCHECK_GT(source, 0);
DCHECK_GT(destination, 0);
float repeat_tiles_count = floorf(destination / source);
if (!repeat_tiles_count)
return std::make_tuple(false, -1);
float space = destination;
space -= source * repeat_tiles_count;
space /= repeat_tiles_count + 1.0;
return std::make_tuple(true, space);
};
if (v_rule == kSpaceTile) {
std::tuple<bool, float> space =
calculate_space_needed(dst_rect.Height(), src_rect.Height());
if (!std::get<0>(space))
return;
spacing.SetHeight(std::get<1>(space));
tile_scale_factor.SetHeight(1.0);
v_phase = src_rect.Y();
v_phase -= spacing.Height();
}
if (h_rule == kSpaceTile) {
std::tuple<bool, float> space =
calculate_space_needed(dst_rect.Width(), src_rect.Width());
if (!std::get<0>(space))
return;
spacing.SetWidth(std::get<1>(space));
tile_scale_factor.SetWidth(1.0);
h_phase = src_rect.X();
h_phase -= spacing.Width();
}
FloatPoint pattern_phase(dst_rect.X() - h_phase, dst_rect.Y() - v_phase);
// TODO(cavalcantii): see crbug.com/662507.
if ((h_rule == kRoundTile) || (v_rule == kRoundTile)) {
ScopedInterpolationQuality interpolation_quality_scope(ctxt,
kInterpolationLow);
DrawPattern(ctxt, src_rect, tile_scale_factor, pattern_phase, op, dst_rect,
FloatSize());
} else {
DrawPattern(ctxt, src_rect, tile_scale_factor, pattern_phase, op, dst_rect,
spacing);
}
}
namespace {
sk_sp<PaintShader> CreatePatternShader(const PaintImage& image,
const SkMatrix& shader_matrix,
SkFilterQuality quality_to_use,
bool should_antialias,
const FloatSize& spacing,
SkShader::TileMode tmx,
SkShader::TileMode tmy) {
if (spacing.IsZero()) {
return PaintShader::MakeImage(image, tmx, tmy, &shader_matrix);
}
// Arbitrary tiling is currently only supported for SkPictureShader, so we use
// that instead of a plain bitmap shader to implement spacing.
const SkRect tile_rect = SkRect::MakeWH(image.width() + spacing.Width(),
image.height() + spacing.Height());
PaintRecorder recorder;
cc::PaintCanvas* canvas = recorder.beginRecording(tile_rect);
PaintFlags flags;
flags.setAntiAlias(should_antialias);
flags.setFilterQuality(quality_to_use);
canvas->drawImage(image, 0, 0, &flags);
return PaintShader::MakePaintRecord(recorder.finishRecordingAsPicture(),
tile_rect, tmx, tmy, &shader_matrix);
}
SkShader::TileMode ComputeTileMode(float left,
float right,
float min,
float max) {
DCHECK(left < right);
return left >= min && right <= max ? SkShader::kClamp_TileMode
: SkShader::kRepeat_TileMode;
}
} // anonymous namespace
void Image::DrawPattern(GraphicsContext& context,
const FloatRect& float_src_rect,
const FloatSize& scale_src_to_dest,
const FloatPoint& phase,
SkBlendMode composite_op,
const FloatRect& dest_rect,
const FloatSize& repeat_spacing) {
TRACE_EVENT0("skia", "Image::drawPattern");
if (dest_rect.IsEmpty())
return; // nothing to draw
PaintImage image = PaintImageForCurrentFrame();
if (!image)
return; // nothing to draw
// The subset_rect is in source image space, unscaled.
IntRect subset_rect = EnclosingIntRect(float_src_rect);
subset_rect.Intersect(IntRect(0, 0, image.width(), image.height()));
if (subset_rect.IsEmpty())
return; // nothing to draw
SkMatrix local_matrix;
// We also need to translate it such that the origin of the pattern is the
// origin of the destination rect, which is what Blink expects. Skia uses
// the coordinate system origin as the base for the pattern. If Blink wants
// a shifted image, it will shift it from there using the localMatrix.
const float adjusted_x =
phase.X() + subset_rect.X() * scale_src_to_dest.Width();
const float adjusted_y =
phase.Y() + subset_rect.Y() * scale_src_to_dest.Height();
local_matrix.setTranslate(SkFloatToScalar(adjusted_x),
SkFloatToScalar(adjusted_y));
// Apply the scale to have the subset correctly fill the destination.
local_matrix.preScale(scale_src_to_dest.Width(), scale_src_to_dest.Height());
// Fetch this now as subsetting may swap the image.
auto image_id = image.GetSkImage()->uniqueID();
image = PaintImageBuilder::WithCopy(std::move(image))
.make_subset(subset_rect)
.TakePaintImage();
if (!image)
return;
const FloatSize tile_size(
image.width() * scale_src_to_dest.Width() + repeat_spacing.Width(),
image.height() * scale_src_to_dest.Height() + repeat_spacing.Height());
const auto tmx = ComputeTileMode(dest_rect.X(), dest_rect.MaxX(), adjusted_x,
adjusted_x + tile_size.Width());
const auto tmy = ComputeTileMode(dest_rect.Y(), dest_rect.MaxY(), adjusted_y,
adjusted_y + tile_size.Height());
SkFilterQuality quality_to_use =
context.ComputeFilterQuality(this, dest_rect, FloatRect(subset_rect));
sk_sp<PaintShader> tile_shader = CreatePatternShader(
image, local_matrix, quality_to_use, context.ShouldAntialias(),
FloatSize(repeat_spacing.Width() / scale_src_to_dest.Width(),
repeat_spacing.Height() / scale_src_to_dest.Height()),
tmx, tmy);
PaintFlags flags = context.FillFlags();
// If the shader could not be instantiated (e.g. non-invertible matrix),
// draw transparent.
// Note: we can't simply bail, because of arbitrary blend mode.
flags.setColor(tile_shader ? SK_ColorBLACK : SK_ColorTRANSPARENT);
flags.setBlendMode(composite_op);
flags.setFilterQuality(quality_to_use);
flags.setShader(std::move(tile_shader));
context.DrawRect(dest_rect, flags);
StartAnimation();
if (CurrentFrameIsLazyDecoded()) {
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
"Draw LazyPixelRef", TRACE_EVENT_SCOPE_THREAD,
"LazyPixelRef", image_id);
}
}
scoped_refptr<Image> Image::ImageForDefaultFrame() {
scoped_refptr<Image> image(this);
return image;
}
PaintImageBuilder Image::CreatePaintImageBuilder() {
auto animation_type = MaybeAnimated() ? PaintImage::AnimationType::ANIMATED
: PaintImage::AnimationType::STATIC;
return PaintImageBuilder::WithDefault()
.set_id(stable_image_id_)
.set_animation_type(animation_type)
.set_is_multipart(is_multipart_);
}
bool Image::ApplyShader(PaintFlags& flags, const SkMatrix& local_matrix) {
// Default shader impl: attempt to build a shader based on the current frame
// SkImage.
PaintImage image = PaintImageForCurrentFrame();
if (!image)
return false;
flags.setShader(PaintShader::MakeImage(image, SkShader::kRepeat_TileMode,
SkShader::kRepeat_TileMode,
&local_matrix));
if (!flags.HasShader())
return false;
// Animation is normally refreshed in draw() impls, which we don't call when
// painting via shaders.
StartAnimation();
return true;
}
SkBitmap Image::AsSkBitmapForCurrentFrame(
RespectImageOrientationEnum should_respect_image_orientation) {
PaintImage paint_image = PaintImageForCurrentFrame();
if (!paint_image)
return {};
if (should_respect_image_orientation == kRespectImageOrientation &&
IsBitmapImage()) {
ImageOrientation orientation =
ToBitmapImage(this)->CurrentFrameOrientation();
paint_image = DragImage::ResizeAndOrientImage(paint_image, orientation);
if (!paint_image)
return {};
}
sk_sp<SkImage> sk_image = paint_image.GetSkImage();
if (!sk_image)
return {};
SkBitmap bitmap;
sk_image->asLegacyBitmap(&bitmap);
return bitmap;
}
} // namespace blink