clipped-image.cpp 6.97 KB
/*
 * Copyright (c) 2016 Samsung Electronics Co., Ltd.
 *
 * 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.
 *
 */

// HEADER
#include "clipped-image.h"

// EXTERNAL INCLUDES
#include <dali-toolkit/dali-toolkit.h>

namespace ClippedImage
{

using namespace Dali;
using namespace Dali::Toolkit;

namespace
{

const char * const DELTA_PROPERTY_NAME( "uDelta" ); ///< Name of uniform used to mix the Circle and Quad geometries.

/**
 * @brief This vertex-shader mixes in the quad and circle geometry depending on the value of uDelta.
 *
 * uDelta is used to mix in the Circle and the Quad positions.
 * If uDelta is 0.0f, then the circle position is adopted and if it is 1.0f, then the quad position is adopted.
 */
const char * VERTEX_SHADER = DALI_COMPOSE_SHADER(
  attribute mediump vec2  aPositionCircle;\n
  attribute mediump vec2  aPositionQuad;\n
  uniform   mediump float uDelta;
  uniform mediump mat4    uMvpMatrix;\n
  uniform mediump vec3    uSize;\n
  \n
  void main()\n
  {\n
    mediump vec4 vertexPosition = vec4(mix(aPositionCircle,aPositionQuad,uDelta), 0.0, 1.0);\n
    vertexPosition.xyz *= uSize;\n
    gl_Position = uMvpMatrix * vertexPosition;\n
  }\n
);

/**
 * @brief This fragment-shader does not output anything. It's for a control which is just going to clip as specified in the vertex shader.
 */
const char * FRAGMENT_SHADER = DALI_COMPOSE_SHADER(
  void main()\n
  {\n
    gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);\n
  }\n
);

/**
 * @brief Creates the shader required for the clipped image
 * @return A reference to a static handle to a shader object (only created when first called).
 */
Shader& CreateShader()
{
  // Only need to create it once
  // The issue with using a static is that the shader will only get destroyed at application destruction.
  // This is OK for a simple use cases such as this example, but for more polished applications, the shader creation/destruction needs to
  // be managed by the application writer.
  static Shader shader;

  if( !shader )
  {
    shader = Shader::New( VERTEX_SHADER, FRAGMENT_SHADER );
  }

  return shader;
}

/**
 * @brief Creates the geometry required for the clipped image
 * @return A reference to a static handle to a geometry object (only created when first called).
 */
Geometry& CreateGeometry()
{
  // Only need to create it once
  // The issue with using a static is that the geometry will only get destroyed at application destruction.
  // This is OK for a simple use cases such as this example, but for more polished applications, the geometry creation/destruction needs to
  // be managed by the application writer.
  static Geometry geometry;

  if ( !geometry )
  {
    const int vertexCount = 34; // Needs to be 4n plus 2 where n is a positive integer above 4

    // Create the circle geometry

    // Radius is bound to actor's dimensions so this should not be increased.
    // If a bigger circle is required then the actor size should be increased.
    const float radius = 0.5f;
    const Vector2 center = Vector2::ZERO;

    // Create a buffer for vertex data
    Vector2 circleBuffer[vertexCount];
    int idx = 0;

    // Center vertex for triangle fan
    circleBuffer[ idx++ ] = center;

    // Outer vertices of the circle
    const int outerVertexCount = vertexCount - 1;

    for ( int i = 0; i < outerVertexCount; ++i )
    {
      const float percent = ( i / static_cast< float >( outerVertexCount - 1 ) );
      const float rad = percent * 2.0f * Math::PI;

      // Vertex position
      Vector2 outer;
      outer.x = center.x + radius * cos( rad );
      outer.y = center.y + radius * sin( rad );

      circleBuffer[ idx++ ] = outer;
    }

    Property::Map circleVertexFormat;
    circleVertexFormat["aPositionCircle"] = Property::VECTOR2;
    PropertyBuffer circleVertices = PropertyBuffer::New( circleVertexFormat );
    circleVertices.SetData( circleBuffer, vertexCount );

    // Create the Quad Geometry
    Vector2 quadBuffer[vertexCount];
    idx = 0;
    quadBuffer[ idx++ ]  = center;

    const size_t vertsPerSide = ( vertexCount - 2 ) / 4;
    Vector2 outer( 0.5f, 0.0f );
    quadBuffer[ idx++ ]  = outer;
    float incrementPerBuffer = 1.0f / static_cast< float >( vertsPerSide );

    for( size_t i = 0; i < vertsPerSide && outer.y < 0.5f; ++i )
    {
      outer.y += incrementPerBuffer;
      quadBuffer[ idx++ ] = outer;
    }

    for( size_t i = 0; i < vertsPerSide && outer.x > -0.5f; ++i )
    {
      outer.x -= incrementPerBuffer;
      quadBuffer[ idx++ ] = outer;
    }

    for( size_t i = 0; i < vertsPerSide && outer.y > -0.5f; ++i )
    {
      outer.y -= incrementPerBuffer;
      quadBuffer[ idx++ ] = outer;
    }

    for( size_t i = 0; i < vertsPerSide && outer.x < 0.5f; ++i )
    {
      outer.x += incrementPerBuffer;
      quadBuffer[ idx++ ] = outer;
    }

    for( size_t i = 0; i < vertsPerSide && outer.y < 0.0f; ++i )
    {
      outer.y += incrementPerBuffer;
      quadBuffer[ idx++ ] = outer;
    }

    Property::Map vertexFormat;
    vertexFormat["aPositionQuad"] = Property::VECTOR2;
    PropertyBuffer quadVertices2 = PropertyBuffer::New( vertexFormat );
    quadVertices2.SetData( quadBuffer, vertexCount );

    // Create the geometry object itself
    geometry = Geometry::New();
    geometry.AddVertexBuffer( circleVertices );
    geometry.AddVertexBuffer( quadVertices2 );
    geometry.SetType( Geometry::TRIANGLE_FAN );
  }

  return geometry;
}

} // unnamed namespace

const float CIRCLE_GEOMETRY = 0.0f;
const float QUAD_GEOMETRY = 1.0f;

Dali::Toolkit::Control Create( const std::string& imagePath, Property::Index& propertyIndex )
{
  // Create a control which whose geometry will be morphed between a circle and a quad
  Control clippedImage = Control::New();
  clippedImage.SetProperty( Actor::Property::CLIPPING_MODE, ClippingMode::CLIP_CHILDREN );

  // Create the required renderer and add to the clipped image control
  Renderer renderer = Renderer::New( CreateGeometry(), CreateShader() );
  renderer.SetProperty( Renderer::Property::BLEND_MODE, BlendMode::ON );
  clippedImage.AddRenderer( renderer );

  // Register the property on the clipped image control which will allow animations between a circle and a quad
  propertyIndex = clippedImage.RegisterProperty( DELTA_PROPERTY_NAME, 0.0f );

  // Add the actual image to the control
  Control image = ImageView::New( imagePath );
  image.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
  image.SetParentOrigin( ParentOrigin::CENTER );
  image.SetAnchorPoint( AnchorPoint::CENTER );
  clippedImage.Add( image );

  return clippedImage;
}

} // namespace ClippedImage