blob: 15eff1c6c7444fc9bc56ca2ccc52a7ab86924695 [file] [log] [blame]
// Copyright 2016 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/renderer/rasterizer/blitter/linear_gradient.h"
#include <algorithm>
#include "cobalt/base/polymorphic_downcast.h"
#include "cobalt/render_tree/brush.h"
#include "cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.h"
#include "cobalt/renderer/rasterizer/blitter/render_state.h"
#include "cobalt/renderer/rasterizer/blitter/skia_blitter_conversions.h"
#include "cobalt/renderer/rasterizer/skia/conversions.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/core/SkShader.h"
#include "third_party/skia/include/effects/SkGradientShader.h"
#if SB_API_VERSION < 12 && SB_HAS(BLITTER)
namespace {
using cobalt::render_tree::ColorRGBA;
using cobalt::render_tree::ColorStopList;
using cobalt::render_tree::LinearGradientBrush;
using cobalt::renderer::rasterizer::blitter::RenderState;
using cobalt::renderer::rasterizer::blitter::SkiaToBlitterPixelFormat;
using cobalt::renderer::rasterizer::blitter::RectFToBlitterRect;
using cobalt::renderer::rasterizer::blitter::LinearGradientCache;
using cobalt::renderer::rasterizer::skia::SkiaColorStops;
int GetPixelOffsetInBytes(const SkImageInfo& image_info,
const SbBlitterPixelData& pixel_data) {
if (image_info.width() == 1)
return SbBlitterGetPixelDataPitchInBytes(pixel_data);
SbBlitterPixelDataFormat pixel_format =
SkiaToBlitterPixelFormat(image_info.colorType());
return SbBlitterBytesPerPixelForFormat(pixel_format);
}
void ColorStopToPixelData(SbBlitterPixelDataFormat pixel_format,
const ColorRGBA& color_stop, uint8_t* pixel_data_out,
uint8_t* pixel_data_out_end) {
DCHECK(pixel_data_out_end >=
(pixel_data_out + SbBlitterBytesPerPixelForFormat(pixel_format)));
uint8_t final_b(color_stop.rgb8_b());
uint8_t final_g(color_stop.rgb8_g());
uint8_t final_r(color_stop.rgb8_r());
uint8_t final_a(color_stop.rgb8_a());
switch (pixel_format) {
case kSbBlitterPixelDataFormatARGB8:
pixel_data_out[0] = final_a;
pixel_data_out[1] = final_r;
pixel_data_out[2] = final_g;
pixel_data_out[3] = final_b;
break;
case kSbBlitterPixelDataFormatBGRA8:
pixel_data_out[0] = final_b;
pixel_data_out[1] = final_g;
pixel_data_out[2] = final_r;
pixel_data_out[3] = final_a;
break;
case kSbBlitterPixelDataFormatRGBA8:
pixel_data_out[0] = final_r;
pixel_data_out[1] = final_g;
pixel_data_out[2] = final_b;
pixel_data_out[3] = final_a;
break;
default:
NOTREACHED() << "Unknown or unsupported pixel format." << pixel_format;
break;
}
}
// This function uses Skia to render a gradient into the pixel data pointed
// at pixel_data_begin. This function is intended as a fallback in case when
// RenderSimpleGradient is unable to render the gradient.
void RenderComplexLinearGradient(const LinearGradientBrush& brush,
const ColorStopList& color_stops, int width,
int height, const SkImageInfo& image_info,
uint8_t* pixel_data_begin,
int pitch_in_bytes) {
SkBitmap bitmap;
bitmap.installPixels(image_info, pixel_data_begin, pitch_in_bytes);
SkCanvas canvas(bitmap);
canvas.clear(SkColorSetARGB(0, 0, 0, 0));
SkPoint points[2];
points[0].fX = 0;
points[0].fY = 0;
points[1].fX = 0;
points[1].fY = 0;
// The -1 offset is easiest to think about in a 2 pixel case:
// If width = 2, then the ending coordinate should be 1.
if (brush.IsHorizontal()) {
if (brush.dest().x() < brush.source().x()) {
points[0].fX = width - 1; // Right to Left.
} else {
points[1].fX = width - 1; // Left to Right.
}
} else {
if (brush.dest().y() < brush.source().y()) {
points[0].fY = height - 1; // High to Low.
} else {
points[1].fY = height - 1; // Low to High.
}
}
SkiaColorStops skia_color_stops(color_stops);
sk_sp<SkShader> shader(SkGradientShader::MakeLinear(
points, skia_color_stops.colors.data(), skia_color_stops.positions.data(),
skia_color_stops.size(), SkTileMode::kClamp,
SkGradientShader::kInterpolateColorsInPremul_Flag, NULL));
SkPaint paint;
paint.setShader(shader);
canvas.drawPaint(paint);
}
// This function tries to render a "simple" gradient into the pixel data pointed
// to in the range [pixel_data_begin, pixel_data_end). Simple gradients here
// are defined as has following properties:
// 1. Vertical or horizontal gradient.
// 2. Two color stops only -- one at position 0, and one at position 1.
//
// Having these conditions greatly simplifies the logic, and allows us to avoid
// the overhead of dealing with Skia.
//
// Returns: true if it could successfully render, false if optimal conditions
// are not met. If false is returned, nothing is written to the pixel data
// buffer.
bool RenderSimpleGradient(const LinearGradientBrush& brush,
const ColorStopList& color_stops, int width,
int height, SbBlitterPixelDataFormat pixel_format,
uint8_t* pixel_data_begin, uint8_t* pixel_data_end,
int pixel_offset_in_bytes) {
if (color_stops.size() != 2) {
return false;
}
const float small_value(1E-6);
if ((color_stops[0].position < -small_value) ||
(color_stops[0].position > small_value)) {
return false;
}
if ((color_stops[1].position < 1 - small_value) ||
(color_stops[1].position > 1 + small_value)) {
return false;
}
const int number_pixels = width * height;
if ((number_pixels < 2) || (width < 0) || (height < 0)) {
return false;
}
ColorRGBA start_color = color_stops[0].color;
ColorRGBA end_color = color_stops[1].color;
// Swap start and end colors if the gradient direction is right to left or
// down to up.
if (brush.IsHorizontal()) {
if (brush.dest().x() < brush.source().x()) {
std::swap(start_color, end_color);
}
} else {
if (brush.dest().y() < brush.source().y()) {
std::swap(start_color, end_color);
}
}
ColorRGBA color_step((end_color - start_color) / (number_pixels - 1));
// We add the color_stop * 0.5f to match pixel positioning in software Skia.
ColorRGBA current_color(start_color + color_step * 0.5f);
ColorStopToPixelData(pixel_format, current_color, pixel_data_begin,
pixel_data_end);
uint8_t* second_pixel = pixel_data_begin + pixel_offset_in_bytes;
uint8_t* last_pixel =
(pixel_data_begin + (number_pixels - 1) * pixel_offset_in_bytes);
for (uint8_t* current_pixel = second_pixel; current_pixel != last_pixel;
current_pixel += pixel_offset_in_bytes) {
current_color += color_step;
ColorStopToPixelData(pixel_format, current_color, current_pixel,
pixel_data_end);
}
ColorStopToPixelData(pixel_format, end_color, last_pixel, pixel_data_end);
return true;
}
void RenderOptimizedLinearGradient(SbBlitterDevice device,
SbBlitterContext context,
const RenderState& render_state,
cobalt::math::RectF rect,
const LinearGradientBrush& brush,
LinearGradientCache* linear_gradient_cache) {
DCHECK(linear_gradient_cache);
if ((rect.width() == 0) && (rect.height() == 0)) return;
DCHECK(brush.IsVertical() || brush.IsHorizontal());
SbBlitterSurface surface = kSbBlitterInvalidSurface;
if (linear_gradient_cache) {
surface = linear_gradient_cache->Get(brush);
}
// The main strategy here is to create a 1D image, and then calculate
// the gradient values in software. If the gradient is simple, this can be
// done with optimized function (RenderSimpleGradient), which avoids calling
// Skia (and thus is faster). Otherwise, we call RenderComplexLinearGradient,
// which uses Skia.
// Once a gradient is created, a SbBlitterSurface is created and a rectangle
// is blitted using the blitter API.
int width = brush.IsHorizontal()
? std::abs(brush.dest().x() - brush.source().x())
: 1;
int height =
brush.IsVertical() ? std::abs(brush.dest().y() - brush.source().y()) : 1;
if (SbBlitterIsSurfaceValid(surface) == false) {
SkImageInfo image_info = SkImageInfo::MakeN32Premul(width, height);
SbBlitterPixelDataFormat pixel_format =
SkiaToBlitterPixelFormat(image_info.colorType());
if (!SbBlitterIsPixelFormatSupportedByPixelData(device, pixel_format)) {
NOTREACHED() << "Pixel Format is not supported by Pixel Data";
return;
}
SbBlitterPixelData pixel_data =
SbBlitterCreatePixelData(device, width, height, pixel_format);
if (SbBlitterIsPixelDataValid(pixel_data)) {
int bytes_per_pixel = GetPixelOffsetInBytes(image_info, pixel_data);
int pitch_in_bytes = SbBlitterGetPixelDataPitchInBytes(pixel_data);
uint8_t* pixel_data_begin =
static_cast<uint8_t*>(SbBlitterGetPixelDataPointer(pixel_data));
uint8_t* pixel_data_end = pixel_data_begin + pitch_in_bytes * height;
const ColorStopList& color_stops(brush.color_stops());
int pixel_offset_in_bytes =
(width == 1) ? pitch_in_bytes : bytes_per_pixel;
// Try rendering without Skia first.
bool render_successful = RenderSimpleGradient(
brush, color_stops, width, height, pixel_format, pixel_data_begin,
pixel_data_end, pixel_offset_in_bytes);
if (!render_successful) {
RenderComplexLinearGradient(brush, color_stops, width, height,
image_info, pixel_data_begin,
pitch_in_bytes);
}
surface = SbBlitterCreateSurfaceFromPixelData(device, pixel_data);
linear_gradient_cache->Put(brush, surface);
}
}
if (surface) {
bool need_blending = false;
for (ColorStopList::const_iterator it = brush.color_stops().begin();
it != brush.color_stops().end(); ++it) {
if (it->color.HasAlpha()) {
need_blending = true;
break;
}
}
SbBlitterSetBlending(context, need_blending);
SbBlitterSetModulateBlitsWithColor(context, false);
cobalt::math::Rect transformed_rect = cobalt::math::Rect::RoundFromRectF(
render_state.transform.TransformRect(rect));
// It may be the case that the linear gradient is larger than the rect.
SbBlitterRect source_rect;
if (height == 1) {
int left = rect.x() - std::min(brush.source().x(), brush.dest().x());
source_rect = SbBlitterMakeRect(left, 0, rect.width(), 1);
} else {
int top = rect.y() - std::min(brush.source().y(), brush.dest().y());
source_rect = SbBlitterMakeRect(0, top, 1, rect.height());
}
SbBlitterRect dest_rect =
SbBlitterMakeRect(transformed_rect.x(), transformed_rect.y(),
transformed_rect.width(), transformed_rect.height());
SbBlitterBlitRectToRect(context, surface, source_rect, dest_rect);
}
}
void RenderColoredRect(SbBlitterDevice device, SbBlitterContext context,
const RenderState& render_state, const ColorRGBA& color,
const cobalt::math::RectF& rect) {
SbBlitterSetBlending(context, color.a() < 1.0f);
SbBlitterSetColor(context,
SbBlitterColorFromRGBA(color.rgb8_r(), color.rgb8_g(),
color.rgb8_b(), color.rgb8_a()));
SbBlitterFillRect(
context, RectFToBlitterRect(render_state.transform.TransformRect(rect)));
}
} // namespace
namespace cobalt {
namespace renderer {
namespace rasterizer {
namespace blitter {
using render_tree::LinearGradientBrush;
bool RenderLinearGradient(SbBlitterDevice device, SbBlitterContext context,
const RenderState& render_state,
const render_tree::RectNode& rect_node,
LinearGradientCache* linear_gradient_cache) {
DCHECK(rect_node.data().background_brush);
const LinearGradientBrush* const linear_gradient_brush =
base::polymorphic_downcast<LinearGradientBrush*>(
rect_node.data().background_brush.get());
if (!linear_gradient_brush) return false;
// Currently, only vertical and horizontal gradients are accelerated.
math::RectF content_rect = rect_node.data().rect;
if (linear_gradient_brush->IsVertical()) {
// Render solid-colored rect(s) to fill any gaps between the content_rect
// and the gradient.
float top = linear_gradient_brush->source().y();
float bottom = linear_gradient_brush->dest().y();
ColorRGBA top_color = linear_gradient_brush->color_stops().front().color;
ColorRGBA bottom_color = linear_gradient_brush->color_stops().back().color;
if (top > bottom) {
std::swap(top, bottom);
std::swap(top_color, bottom_color);
}
if (top > content_rect.y()) {
float gap = top - content_rect.y();
RenderColoredRect(device, context, render_state, top_color,
math::RectF(content_rect.x(), content_rect.y(),
content_rect.width(), gap));
content_rect.set_y(top);
content_rect.set_height(content_rect.height() - gap);
}
if (bottom < content_rect.bottom()) {
float gap = content_rect.bottom() - bottom;
RenderColoredRect(
device, context, render_state, bottom_color,
math::RectF(content_rect.x(), bottom, content_rect.width(), gap));
content_rect.set_height(content_rect.height() - gap);
}
} else if (linear_gradient_brush->IsHorizontal()) {
// Render solid-colored rect(s) to fill any gaps between the content_rect
// and the gradient.
float left = linear_gradient_brush->source().x();
float right = linear_gradient_brush->dest().x();
ColorRGBA left_color = linear_gradient_brush->color_stops().front().color;
ColorRGBA right_color = linear_gradient_brush->color_stops().back().color;
if (left > right) {
std::swap(left, right);
std::swap(left_color, right_color);
}
if (left > content_rect.x()) {
float gap = left - content_rect.x();
RenderColoredRect(device, context, render_state, left_color,
math::RectF(content_rect.x(), content_rect.y(), gap,
content_rect.height()));
content_rect.set_x(left);
content_rect.set_width(content_rect.width() - gap);
}
if (right < content_rect.right()) {
float gap = content_rect.right() - right;
RenderColoredRect(
device, context, render_state, right_color,
math::RectF(right, content_rect.y(), gap, content_rect.height()));
content_rect.set_width(content_rect.width() - gap);
}
} else {
// Angled gradients are not supported by the optimized path.
return false;
}
RenderOptimizedLinearGradient(device, context, render_state, content_rect,
*linear_gradient_brush, linear_gradient_cache);
return true;
}
} // namespace blitter
} // namespace rasterizer
} // namespace renderer
} // namespace cobalt
#endif // SB_API_VERSION < 12 && SB_HAS(BLITTER)