| /* |
| * Copyright 2016 Google Inc. 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/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_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::RectFToRect; |
| 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 |
| // RenderOptimizedLinearGradient 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); |
| SkAutoTUnref<SkShader> shader(SkGradientShader::CreateLinear( |
| points, skia_color_stops.colors.data(), skia_color_stops.positions.data(), |
| skia_color_stops.size(), SkShader::kClamp_TileMode, |
| 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) { |
| if ((rect.width() == 0) && (rect.height() == 0)) return; |
| |
| DCHECK(brush.IsVertical() || brush.IsHorizontal()); |
| |
| // 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 |
| // one with optimized function (RenderSimpleGradient), which avoids calling |
| // Skia (and thus is faster). Otherwise, we call RenderComplexLinearGradient, |
| // which uses Skia. |
| |
| // One a gradient is created, a SbBlitterSurface is created and a rectangle |
| // is blitted using the blitter API. |
| int width = brush.IsHorizontal() ? rect.width() : 1; |
| int height = brush.IsVertical() ? rect.height() : 1; |
| |
| 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); |
| |
| 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; |
| |
| if (pixel_data) { |
| const ColorStopList& color_stops(brush.color_stops()); |
| |
| int pixel_offset_in_bytes = (width == 1) ? pitch_in_bytes : bytes_per_pixel; |
| 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); |
| } |
| |
| SbBlitterSurface surface = |
| SbBlitterCreateSurfaceFromPixelData(device, pixel_data); |
| |
| if (surface) { |
| SbBlitterSetBlending(context, true); |
| SbBlitterSetModulateBlitsWithColor(context, false); |
| cobalt::math::Rect transformed_rect = |
| RectFToRect(render_state.transform.TransformRect(rect)); |
| SbBlitterRect source_rect = SbBlitterMakeRect(0, 0, width, height); |
| SbBlitterRect dest_rect = SbBlitterMakeRect( |
| transformed_rect.x(), transformed_rect.y(), transformed_rect.width(), |
| transformed_rect.height()); |
| SbBlitterBlitRectToRect(context, surface, source_rect, dest_rect); |
| |
| SbBlitterDestroySurface(surface); |
| } |
| } |
| } |
| } // 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) { |
| 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. |
| if ((linear_gradient_brush->IsVertical() || |
| linear_gradient_brush->IsHorizontal()) == false) |
| return false; |
| |
| RenderOptimizedLinearGradient(device, context, render_state, |
| rect_node.data().rect, *linear_gradient_brush); |
| return true; |
| } |
| |
| } // namespace blitter |
| } // namespace rasterizer |
| } // namespace renderer |
| } // namespace cobalt |
| |
| #endif // #if SB_HAS(BLITTER) |