2 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include "image-renderer.h"
22 #include <cstring> // for strncasecmp
23 #include <dali/public-api/images/resource-image.h>
24 #include <dali/integration-api/debug.h>
27 #include <dali-toolkit/internal/controls/renderers/renderer-factory-impl.h>
28 #include <dali-toolkit/internal/controls/renderers/renderer-factory-cache.h>
29 #include <dali-toolkit/internal/controls/renderers/control-renderer-impl.h>
30 #include <dali-toolkit/internal/controls/renderers/control-renderer-data-impl.h>
31 #include <dali-toolkit/internal/controls/renderers/image-atlas-manager.h>
44 const char HTTP_URL[] = "http://";
45 const char HTTPS_URL[] = "https://";
47 const char * const RENDERER_TYPE("rendererType");
48 const char * const RENDERER_TYPE_VALUE("imageRenderer");
51 const char * const IMAGE_URL_NAME( "imageUrl" );
52 const char * const IMAGE_FITTING_MODE( "imageFittingMode" );
53 const char * const IMAGE_SAMPLING_MODE( "imageSamplingMode" );
54 const char * const IMAGE_DESIRED_WIDTH( "imageDesiredWidth" );
55 const char * const IMAGE_DESIRED_HEIGHT( "imageDesiredHeight" );
58 const char * const SHRINK_TO_FIT("shrinkToFit");
59 const char * const SCALE_TO_FILL("scaleToFill");
60 const char * const FIT_WIDTH("fitWidth");
61 const char * const FIT_HEIGHT("fitHeight");
62 const char * const DEFAULT("default");
65 const char * const BOX("box");
66 const char * const NEAREST("nearest");
67 const char * const LINEAR("linear");
68 const char * const BOX_THEN_NEAREST("boxThenNearest");
69 const char * const BOX_THEN_LINEAR("boxThenLinear");
70 const char * const NO_FILTER("noFilter");
71 const char * const DONT_CARE("dontCare");
73 const std::string TEXTURE_UNIFORM_NAME = "sTexture";
74 const std::string TEXTURE_RECT_UNIFORM_NAME = "uTextureRect";
75 const Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f);
77 const char* VERTEX_SHADER = DALI_COMPOSE_SHADER(
78 attribute mediump vec2 aPosition;\n
79 varying mediump vec2 vTexCoord;\n
80 uniform mediump mat4 uMvpMatrix;\n
81 uniform mediump vec3 uSize;\n
82 uniform mediump vec4 uTextureRect;\n
86 mediump vec4 vertexPosition = vec4(aPosition, 0.0, 1.0);\n
87 vertexPosition.xyz *= uSize;\n
88 vertexPosition = uMvpMatrix * vertexPosition;\n
90 vTexCoord = mix( uTextureRect.xy, uTextureRect.zw, aPosition + vec2(0.5));\n
91 gl_Position = vertexPosition;\n
95 const char* FRAGMENT_SHADER = DALI_COMPOSE_SHADER(
96 varying mediump vec2 vTexCoord;\n
97 uniform sampler2D sTexture;\n
98 uniform lowp vec4 uColor;\n
102 gl_FragColor = texture2D( sTexture, vTexCoord ) * uColor;\n
106 Geometry GenerateGeometry( const Vector< Vector2 >& vertices, const Vector< unsigned int >& indices )
108 Property::Map vertexFormat;
109 vertexFormat[ "aPosition" ] = Property::VECTOR2;
110 PropertyBuffer vertexPropertyBuffer = PropertyBuffer::New( vertexFormat, vertices.Size() );
111 if( vertices.Size() > 0 )
113 vertexPropertyBuffer.SetData( &vertices[ 0 ] );
116 Property::Map indexFormat;
117 indexFormat[ "indices" ] = Property::INTEGER;
118 PropertyBuffer indexPropertyBuffer = PropertyBuffer::New( indexFormat, indices.Size() );
119 if( indices.Size() > 0 )
121 indexPropertyBuffer.SetData( &indices[ 0 ] );
124 // Create the geometry object
125 Geometry geometry = Geometry::New();
126 geometry.AddVertexBuffer( vertexPropertyBuffer );
127 geometry.SetIndexBuffer( indexPropertyBuffer );
128 geometry.SetGeometryType( Geometry::TRIANGLE_STRIP );
133 Geometry CreateGeometry( RendererFactoryCache& factoryCache, ImageDimensions gridSize )
137 if( gridSize == ImageDimensions( 1, 1 ) )
139 geometry = factoryCache.GetGeometry( RendererFactoryCache::QUAD_GEOMETRY );
142 geometry = factoryCache.CreateQuadGeometry();
143 factoryCache.SaveGeometry( RendererFactoryCache::QUAD_GEOMETRY, geometry );
148 uint16_t gridWidth = gridSize.GetWidth();
149 uint16_t gridHeight = gridSize.GetHeight();
152 Vector< Vector2 > vertices;
153 vertices.Reserve( ( gridWidth + 1 ) * ( gridHeight + 1 ) );
155 for( int y = 0; y < gridHeight + 1; ++y )
157 for( int x = 0; x < gridWidth + 1; ++x )
159 vertices.PushBack( Vector2( (float)x/gridWidth - 0.5f, (float)y/gridHeight - 0.5f) );
164 Vector< unsigned int > indices;
165 indices.Reserve( (gridWidth+2)*gridHeight*2 - 2);
167 for( unsigned int row = 0u; row < gridHeight; ++row )
169 unsigned int rowStartIndex = row*(gridWidth+1u);
170 unsigned int nextRowStartIndex = rowStartIndex + gridWidth +1u;
172 if( row != 0u ) // degenerate index on non-first row
174 indices.PushBack( rowStartIndex );
177 for( unsigned int column = 0u; column < gridWidth+1u; column++) // main strip
179 indices.PushBack( rowStartIndex + column);
180 indices.PushBack( nextRowStartIndex + column);
183 if( row != gridHeight-1u ) // degenerate index on non-last row
185 indices.PushBack( nextRowStartIndex + gridWidth );
189 return GenerateGeometry( vertices, indices );
195 } //unnamed namespace
197 ImageRenderer::ImageRenderer( RendererFactoryCache& factoryCache, ImageAtlasManager& atlasManager )
198 : ControlRenderer( factoryCache ),
199 mAtlasManager( atlasManager ),
200 mTextureRect( FULL_TEXTURE_RECT ),
202 mFittingMode( FittingMode::DEFAULT ),
203 mSamplingMode( SamplingMode::DEFAULT )
207 ImageRenderer::~ImageRenderer()
211 void ImageRenderer::DoInitialize( Actor& actor, const Property::Map& propertyMap )
213 std::string oldImageUrl = mImageUrl;
215 Property::Value* imageURLValue = propertyMap.Find( IMAGE_URL_NAME );
218 imageURLValue->Get( mImageUrl );
219 if( !mImageUrl.empty() )
224 Property::Value* fittingValue = propertyMap.Find( IMAGE_FITTING_MODE );
228 fittingValue->Get( fitting );
230 mFittingMode = FittingMode::DEFAULT;
231 if( fitting == SHRINK_TO_FIT )
233 mFittingMode = FittingMode::SHRINK_TO_FIT;
235 else if( fitting == SCALE_TO_FILL )
237 mFittingMode = FittingMode::SCALE_TO_FILL;
239 else if( fitting == FIT_WIDTH )
241 mFittingMode = FittingMode::FIT_WIDTH;
243 else if( fitting == FIT_HEIGHT )
245 mFittingMode = FittingMode::FIT_HEIGHT;
247 else if( fitting == DEFAULT )
249 mFittingMode = FittingMode::DEFAULT;
253 DALI_ASSERT_ALWAYS("Unknown fitting mode");
257 Property::Value* samplingValue = propertyMap.Find( IMAGE_SAMPLING_MODE );
260 std::string sampling;
261 samplingValue->Get( sampling );
263 mSamplingMode = SamplingMode::DEFAULT;
264 if( sampling == BOX )
266 mSamplingMode = SamplingMode::BOX;
268 else if( sampling == NEAREST )
270 mSamplingMode = SamplingMode::NEAREST;
272 else if( sampling == LINEAR )
274 mSamplingMode = SamplingMode::LINEAR;
276 else if( sampling == BOX_THEN_NEAREST )
278 mSamplingMode = SamplingMode::BOX_THEN_NEAREST;
280 else if( sampling == BOX_THEN_LINEAR )
282 mSamplingMode = SamplingMode::BOX_THEN_LINEAR;
284 else if( sampling == NO_FILTER )
286 mSamplingMode = SamplingMode::NO_FILTER;
288 else if( sampling == DONT_CARE )
290 mSamplingMode = SamplingMode::DONT_CARE;
292 else if( sampling == DEFAULT )
294 mSamplingMode = SamplingMode::DEFAULT;
298 DALI_ASSERT_ALWAYS("Unknown sampling mode");
302 int desiredWidth = 0;
303 Property::Value* desiredWidthValue = propertyMap.Find( IMAGE_DESIRED_WIDTH );
304 if( desiredWidthValue )
306 desiredWidthValue->Get( desiredWidth );
309 int desiredHeight = 0;
310 Property::Value* desiredHeightValue = propertyMap.Find( IMAGE_DESIRED_HEIGHT );
311 if( desiredHeightValue )
313 desiredHeightValue->Get( desiredHeight );
316 mDesiredSize = ImageDimensions( desiredWidth, desiredHeight );
319 // remove old renderer if exit
320 if( mImpl->mRenderer )
322 if( actor ) //remove old renderer from actor
324 actor.RemoveRenderer( mImpl->mRenderer );
326 if( !oldImageUrl.empty() ) //clean old renderer from cache
328 CleanCache( oldImageUrl );
332 // if actor is on stage, create new renderer and apply to actor
333 if( actor && actor.OnStage() )
339 void ImageRenderer::SetSize( const Vector2& size )
341 ControlRenderer::SetSize( size );
344 void ImageRenderer::GetNaturalSize( Vector2& naturalSize ) const
348 naturalSize.x = mImage.GetWidth();
349 naturalSize.y = mImage.GetHeight();
352 else if( mDesiredSize.GetWidth()>0 && mDesiredSize.GetHeight()>0)
354 naturalSize.x = mDesiredSize.GetWidth();
355 naturalSize.y = mDesiredSize.GetHeight();
358 else if( !mImageUrl.empty() )
360 ImageDimensions dimentions = ResourceImage::GetImageSize( mImageUrl );
361 naturalSize.x = dimentions.GetWidth();
362 naturalSize.y = dimentions.GetHeight();
366 naturalSize = Vector2::ZERO;
369 void ImageRenderer::SetClipRect( const Rect<int>& clipRect )
371 ControlRenderer::SetClipRect( clipRect );
374 void ImageRenderer::SetOffset( const Vector2& offset )
378 Renderer ImageRenderer::CreateRenderer() const
383 if( !mImpl->mCustomShader )
385 geometry = CreateGeometry( mFactoryCache, ImageDimensions( 1, 1 ) );
386 shader = GetImageShader(mFactoryCache);
390 geometry = CreateGeometry( mFactoryCache, mImpl->mCustomShader->mGridSize );
391 if( mImpl->mCustomShader->mVertexShader.empty() && mImpl->mCustomShader->mFragmentShader.empty() )
393 shader = GetImageShader(mFactoryCache);
397 shader = Shader::New( mImpl->mCustomShader->mVertexShader.empty() ? VERTEX_SHADER : mImpl->mCustomShader->mVertexShader,
398 mImpl->mCustomShader->mFragmentShader.empty() ? FRAGMENT_SHADER : mImpl->mCustomShader->mFragmentShader,
399 mImpl->mCustomShader->mHints );
403 Material material = Material::New( shader );
404 return Renderer::New( geometry, material );
407 void ImageRenderer::InitializeRenderer( const std::string& imageUrl )
409 if( imageUrl.empty() )
414 mImageUrl = imageUrl;
415 mImpl->mRenderer.Reset();
417 if( !mImpl->mCustomShader &&
418 ( strncasecmp( imageUrl.c_str(), HTTP_URL, sizeof(HTTP_URL) -1 ) != 0 ) && // ignore remote images
419 ( strncasecmp( imageUrl.c_str(), HTTPS_URL, sizeof(HTTPS_URL) -1 ) != 0 ) )
421 mImpl->mRenderer = mFactoryCache.GetRenderer( imageUrl );
422 if( !mImpl->mRenderer )
424 Material material = mAtlasManager.Add(mTextureRect, imageUrl, mDesiredSize, mFittingMode, mSamplingMode );
427 Geometry geometry = CreateGeometry( mFactoryCache, ImageDimensions( 1, 1 ) );
428 mImpl->mRenderer = Renderer::New( geometry, material );
429 SetTextureRectUniform(mTextureRect);
431 else // big image, atlasing is not applied
433 mImpl->mRenderer = CreateRenderer();
434 mTextureRect = FULL_TEXTURE_RECT;
435 SetTextureRectUniform(mTextureRect);
437 ResourceImage image = Dali::ResourceImage::New( imageUrl );
438 image.LoadingFinishedSignal().Connect( this, &ImageRenderer::OnImageLoaded );
439 Material material = mImpl->mRenderer.GetMaterial();
440 material.AddTexture( image, TEXTURE_UNIFORM_NAME );
443 mFactoryCache.SaveRenderer( imageUrl, mImpl->mRenderer );
447 Property::Value textureRect = mImpl->mRenderer.GetProperty( mImpl->mRenderer.GetPropertyIndex(TEXTURE_RECT_UNIFORM_NAME) );
448 textureRect.Get( mTextureRect );
450 mImpl->mFlags |= Impl::IS_FROM_CACHE;
454 // for custom shader or remote image, renderer is not cached and atlas is not applied
456 mImpl->mFlags &= ~Impl::IS_FROM_CACHE;
457 mImpl->mRenderer = CreateRenderer();
458 ResourceImage resourceImage = Dali::ResourceImage::New( imageUrl, mDesiredSize, mFittingMode, mSamplingMode );
459 resourceImage.LoadingFinishedSignal().Connect( this, &ImageRenderer::OnImageLoaded );
460 ApplyImageToSampler( resourceImage );
462 // custom vertex shader does not need texture rect uniform
463 if( mImpl->mCustomShader && !mImpl->mCustomShader->mVertexShader.empty() )
468 mTextureRect = FULL_TEXTURE_RECT;
469 SetTextureRectUniform( mTextureRect );
473 void ImageRenderer::InitializeRenderer( const Image& image )
475 mImpl->mFlags &= ~Impl::IS_FROM_CACHE;
482 mImpl->mRenderer = CreateRenderer();
483 ApplyImageToSampler( image );
485 // default shader or custom shader with the default image vertex shader
486 if( !mImpl->mCustomShader || mImpl->mCustomShader->mVertexShader.empty() )
488 mTextureRect = FULL_TEXTURE_RECT;
489 SetTextureRectUniform( mTextureRect );
494 void ImageRenderer::DoSetOnStage( Actor& actor )
496 if( !mImageUrl.empty() )
498 InitializeRenderer( mImageUrl );
502 InitializeRenderer( mImage );
506 void ImageRenderer::DoSetOffStage( Actor& actor )
508 //If we own the image then make sure we release it when we go off stage
509 if( !mImageUrl.empty() )
511 actor.RemoveRenderer( mImpl->mRenderer );
512 CleanCache(mImageUrl);
518 actor.RemoveRenderer( mImpl->mRenderer );
519 mImpl->mRenderer.Reset();
523 void ImageRenderer::DoCreatePropertyMap( Property::Map& map ) const
526 map.Insert( RENDERER_TYPE, RENDERER_TYPE_VALUE );
527 if( !mImageUrl.empty() )
529 map.Insert( IMAGE_URL_NAME, mImageUrl );
530 map.Insert( IMAGE_DESIRED_WIDTH, mDesiredSize.GetWidth() );
531 map.Insert( IMAGE_DESIRED_HEIGHT, mDesiredSize.GetHeight() );
535 map.Insert( IMAGE_DESIRED_WIDTH, static_cast<int>(mImage.GetWidth()) );
536 map.Insert( IMAGE_DESIRED_HEIGHT, static_cast<int>(mImage.GetHeight()) );
538 ResourceImage resourceImage = ResourceImage::DownCast(mImage);
541 map.Insert( IMAGE_URL_NAME, resourceImage.GetUrl() );
545 switch( mFittingMode )
547 case Dali::FittingMode::FIT_HEIGHT:
549 map.Insert( IMAGE_FITTING_MODE, FIT_HEIGHT );
552 case Dali::FittingMode::FIT_WIDTH:
554 map.Insert( IMAGE_FITTING_MODE, FIT_WIDTH );
557 case Dali::FittingMode::SCALE_TO_FILL:
559 map.Insert( IMAGE_FITTING_MODE, SCALE_TO_FILL );
562 case Dali::FittingMode::SHRINK_TO_FIT:
564 map.Insert( IMAGE_FITTING_MODE, SHRINK_TO_FIT );
569 map.Insert( IMAGE_FITTING_MODE, DEFAULT );
574 switch( mSamplingMode )
576 case Dali::SamplingMode::BOX:
578 map.Insert( IMAGE_SAMPLING_MODE, BOX );
581 case Dali::SamplingMode::NEAREST:
583 map.Insert( IMAGE_SAMPLING_MODE, NEAREST );
586 case Dali::SamplingMode::LINEAR:
588 map.Insert( IMAGE_SAMPLING_MODE, LINEAR );
591 case Dali::SamplingMode::BOX_THEN_LINEAR:
593 map.Insert( IMAGE_SAMPLING_MODE, BOX_THEN_LINEAR );
596 case Dali::SamplingMode::BOX_THEN_NEAREST:
598 map.Insert( IMAGE_SAMPLING_MODE, BOX_THEN_NEAREST );
601 case Dali::SamplingMode::NO_FILTER:
603 map.Insert( IMAGE_SAMPLING_MODE, NO_FILTER );
606 case Dali::SamplingMode::DONT_CARE:
608 map.Insert( IMAGE_SAMPLING_MODE, DONT_CARE );
613 map.Insert( IMAGE_SAMPLING_MODE, DEFAULT );
619 Shader ImageRenderer::GetImageShader( RendererFactoryCache& factoryCache )
621 Shader shader = factoryCache.GetShader( RendererFactoryCache::IMAGE_SHADER );
624 shader = Shader::New( VERTEX_SHADER, FRAGMENT_SHADER );
625 factoryCache.SaveShader( RendererFactoryCache::IMAGE_SHADER, shader );
630 void ImageRenderer::SetImage( Actor& actor, const std::string& imageUrl, ImageDimensions size, Dali::FittingMode::Type fittingMode, Dali::SamplingMode::Type samplingMode )
632 if( mImageUrl != imageUrl )
634 std::string oldImageUrl = mImageUrl;
635 mImageUrl = imageUrl;
637 mFittingMode = fittingMode;
638 mSamplingMode = samplingMode;
641 if( mImpl->mRenderer )
643 if( GetIsFromCache() ) // if renderer is from cache, remove the old one
645 //remove old renderer
648 actor.RemoveRenderer( mImpl->mRenderer );
652 if( !oldImageUrl.empty() )
654 CleanCache(oldImageUrl);
657 if( actor && actor.OnStage() ) // if actor on stage, create a new renderer and apply to actor
662 else // if renderer is not from cache, reuse the same renderer and only change the texture
664 ResourceImage image = Dali::ResourceImage::New( imageUrl, mDesiredSize, mFittingMode, mSamplingMode );
665 image.LoadingFinishedSignal().Connect( this, &ImageRenderer::OnImageLoaded );
666 ApplyImageToSampler( image );
672 void ImageRenderer::SetImage( Actor& actor, const Image& image )
674 if( mImage != image )
678 if( mImpl->mRenderer )
680 if( GetIsFromCache() ) // if renderer is from cache, remove the old one
682 //remove old renderer
685 actor.RemoveRenderer( mImpl->mRenderer );
689 if( !mImageUrl.empty() )
691 CleanCache(mImageUrl);
695 if( actor && actor.OnStage() ) // if actor on stage, create a new renderer and apply to actor
700 else // if renderer is not from cache, reuse the same renderer and only change the texture
702 ApplyImageToSampler( image );
707 mDesiredSize = ImageDimensions();
708 mFittingMode = FittingMode::DEFAULT;
709 mSamplingMode = SamplingMode::DEFAULT;
713 void ImageRenderer::ApplyImageToSampler( const Image& image )
717 Material material = mImpl->mRenderer.GetMaterial();
720 int index = material.GetTextureIndex( TEXTURE_UNIFORM_NAME );
723 material.SetTextureImage( index, image );
727 material.AddTexture( image, TEXTURE_UNIFORM_NAME );
732 void ImageRenderer::OnImageLoaded( ResourceImage image )
734 if( image.GetLoadingState() == Dali::ResourceLoadingFailed )
736 Image brokenImage = RendererFactory::GetBrokenRendererImage();
737 if( mImpl->mRenderer )
739 ApplyImageToSampler( brokenImage );
744 void ImageRenderer::SetTextureRectUniform( const Vector4& textureRect )
746 if( mImpl->mRenderer )
748 // Register/Set property.
749 mImpl->mRenderer.RegisterProperty( TEXTURE_RECT_UNIFORM_NAME, textureRect );
753 void ImageRenderer::CleanCache(const std::string& url)
755 Material material = mImpl->mRenderer.GetMaterial();
756 mImpl->mRenderer.Reset();
757 if( mFactoryCache.CleanRendererCache( url ) )
759 mAtlasManager.Remove( material, mTextureRect );
763 } // namespace Internal
765 } // namespace Toolkit