blob: f4bb7ec30cc3cf7eb7d6f3078c3fe69594cede46 [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 "platform/graphics/Image.h"
#include "platform/Length.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/SharedBuffer.h"
#include "platform/geometry/FloatPoint.h"
#include "platform/geometry/FloatRect.h"
#include "platform/geometry/FloatSize.h"
#include "platform/graphics/BitmapImage.h"
#include "platform/graphics/DeferredImageDecoder.h"
#include "platform/graphics/GraphicsContext.h"
#include "platform/graphics/paint/PaintImage.h"
#include "platform/graphics/paint/PaintRecorder.h"
#include "platform/graphics/paint/PaintShader.h"
#include "platform/instrumentation/PlatformInstrumentation.h"
#include "platform/instrumentation/tracing/TraceEvent.h"
#include "platform/network/mime/MIMETypeRegistry.h"
#include "platform/wtf/StdLibExtras.h"
#include "public/platform/Platform.h"
#include "public/platform/WebData.h"
#include "third_party/skia/include/core/SkImage.h"
#include <math.h>
#include <tuple>
namespace blink {
Image::Image(ImageObserver* observer)
: image_observer_disabled_(false),
image_observer_(observer),
stable_image_id_(PaintImage::GetNextId()) {}
Image::~Image() {}
Image* Image::NullImage() {
DCHECK(IsMainThread());
DEFINE_STATIC_REF(Image, null_image, (BitmapImage::Create()));
return null_image;
}
PassRefPtr<Image> Image::LoadPlatformResource(const char* name) {
const WebData& resource = Platform::Current()->LoadResource(name);
if (resource.IsEmpty())
return Image::NullImage();
RefPtr<Image> image = BitmapImage::Create();
image->SetData(resource, true);
return image.Release();
}
bool Image::SupportsType(const String& type) {
return MIMETypeRegistry::IsSupportedImageResourceMIMEType(type);
}
Image::SizeAvailability Image::SetData(PassRefPtr<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);
}
void Image::DrawTiledBackground(GraphicsContext& ctxt,
const FloatRect& dest_rect,
const FloatPoint& src_point,
const FloatSize& scaled_tile_size,
SkBlendMode op,
const FloatSize& repeat_spacing) {
FloatSize intrinsic_tile_size(Size());
if (HasRelativeSize()) {
intrinsic_tile_size.SetWidth(scaled_tile_size.Width());
intrinsic_tile_size.SetHeight(scaled_tile_size.Height());
}
const FloatSize scale(
scaled_tile_size.Width() / intrinsic_tile_size.Width(),
scaled_tile_size.Height() / intrinsic_tile_size.Height());
const FloatRect one_tile_rect = ComputeTileContaining(
dest_rect.Location(), scaled_tile_size, src_point, repeat_spacing);
// Check and see if a single draw of the image can cover the entire area we
// are supposed to tile.
if (one_tile_rect.Contains(dest_rect)) {
const FloatRect visible_src_rect =
ComputeSubsetForTile(one_tile_rect, dest_rect, intrinsic_tile_size);
ctxt.DrawImage(this, dest_rect, &visible_src_rect, op,
kDoNotRespectImageOrientation);
return;
}
FloatRect tile_rect(FloatPoint(), intrinsic_tile_size);
DrawPattern(ctxt, tile_rect, scale, one_tile_rect.Location(), op, dest_rect,
repeat_spacing);
StartAnimation();
}
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)) {
InterpolationQuality previous_interpolation_quality =
ctxt.ImageInterpolationQuality();
ctxt.SetImageInterpolationQuality(kInterpolationLow);
DrawPattern(ctxt, src_rect, tile_scale_factor, pattern_phase, op, dst_rect);
ctxt.SetImageInterpolationQuality(previous_interpolation_quality);
} else {
DrawPattern(ctxt, src_rect, tile_scale_factor, pattern_phase, op, dst_rect,
spacing);
}
StartAnimation();
}
namespace {
sk_sp<PaintShader> CreatePatternShader(const PaintImage& image,
const SkMatrix& shader_matrix,
const PaintFlags& paint,
const FloatSize& spacing,
SkShader::TileMode tmx,
SkShader::TileMode tmy) {
if (spacing.IsZero())
return MakePaintShaderImage(image.sk_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.sk_image()->width() + spacing.Width(),
image.sk_image()->height() + spacing.Height());
PaintRecorder recorder;
PaintCanvas* canvas = recorder.beginRecording(tile_rect);
canvas->drawImage(image, 0, 0, &paint);
return MakePaintShaderRecord(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,
const FloatPoint& phase,
SkBlendMode composite_op,
const FloatRect& dest_rect,
const FloatSize& repeat_spacing) {
TRACE_EVENT0("skia", "Image::drawPattern");
PaintImage image = PaintImageForCurrentFrame();
if (!image)
return;
FloatRect norm_src_rect = float_src_rect;
norm_src_rect.Intersect(
FloatRect(0, 0, image.sk_image()->width(), image.sk_image()->height()));
if (dest_rect.IsEmpty() || norm_src_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 WebKit expects. Skia uses
// the coordinate system origin as the base for the pattern. If WebKit wants
// a shifted image, it will shift it from there using the localMatrix.
const float adjusted_x = phase.X() + norm_src_rect.X() * scale.Width();
const float adjusted_y = phase.Y() + norm_src_rect.Y() * scale.Height();
local_matrix.setTranslate(SkFloatToScalar(adjusted_x),
SkFloatToScalar(adjusted_y));
// Because no resizing occurred, the shader transform should be
// set to the pattern's transform, which just includes scale.
local_matrix.preScale(scale.Width(), scale.Height());
// Fetch this now as subsetting may swap the image.
auto image_id = image.sk_image()->uniqueID();
image =
PaintImage(stable_image_id_,
image.sk_image()->makeSubset(EnclosingIntRect(norm_src_rect)),
image.animation_type(), image.completion_state());
if (!image)
return;
const FloatSize tile_size(
image.sk_image()->width() * scale.Width() + repeat_spacing.Width(),
image.sk_image()->height() * scale.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());
PaintFlags flags = context.FillFlags();
flags.setColor(SK_ColorBLACK);
flags.setBlendMode(composite_op);
flags.setFilterQuality(
context.ComputeFilterQuality(this, dest_rect, norm_src_rect));
flags.setAntiAlias(context.ShouldAntialias());
flags.setShader(
CreatePatternShader(image, local_matrix, flags,
FloatSize(repeat_spacing.Width() / scale.Width(),
repeat_spacing.Height() / scale.Height()),
tmx, tmy));
// 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.
if (!flags.getShader())
flags.setColor(SK_ColorTRANSPARENT);
context.DrawRect(dest_rect, flags);
if (CurrentFrameIsLazyDecoded())
PlatformInstrumentation::DidDrawLazyPixelRef(image_id);
}
PassRefPtr<Image> Image::ImageForDefaultFrame() {
RefPtr<Image> image(this);
return image.Release();
}
PaintImage Image::PaintImageForCurrentFrame() {
auto animation_type = MaybeAnimated() ? PaintImage::AnimationType::ANIMATED
: PaintImage::AnimationType::STATIC;
auto completion_state = CurrentFrameIsComplete()
? PaintImage::CompletionState::DONE
: PaintImage::CompletionState::PARTIALLY_DONE;
return PaintImage(stable_image_id_, ImageForCurrentFrame(), animation_type,
completion_state);
}
bool Image::ApplyShader(PaintFlags& flags, const SkMatrix& local_matrix) {
// Default shader impl: attempt to build a shader based on the current frame
// SkImage.
sk_sp<SkImage> image = ImageForCurrentFrame();
if (!image)
return false;
flags.setShader(image->makeShader(SkShader::kRepeat_TileMode,
SkShader::kRepeat_TileMode, &local_matrix));
if (!flags.getShader())
return false;
// Animation is normally refreshed in draw() impls, which we don't call when
// painting via shaders.
StartAnimation();
return true;
}
FloatRect Image::ComputeTileContaining(const FloatPoint& point,
const FloatSize& tile_size,
const FloatPoint& tile_phase,
const FloatSize& tile_spacing) {
const FloatSize actual_tile_size(tile_size + tile_spacing);
return FloatRect(
FloatPoint(
point.X() + fmodf(fmodf(-tile_phase.X(), actual_tile_size.Width()) -
actual_tile_size.Width(),
actual_tile_size.Width()),
point.Y() + fmodf(fmodf(-tile_phase.Y(), actual_tile_size.Height()) -
actual_tile_size.Height(),
actual_tile_size.Height())),
tile_size);
}
FloatRect Image::ComputeSubsetForTile(const FloatRect& tile,
const FloatRect& dest,
const FloatSize& image_size) {
DCHECK(tile.Contains(dest));
const FloatSize scale(tile.Width() / image_size.Width(),
tile.Height() / image_size.Height());
FloatRect subset = dest;
subset.SetX((dest.X() - tile.X()) / scale.Width());
subset.SetY((dest.Y() - tile.Y()) / scale.Height());
subset.SetWidth(dest.Width() / scale.Width());
subset.SetHeight(dest.Height() / scale.Height());
return subset;
}
} // namespace blink