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 ATLAS_RECT_UNIFORM_NAME = "uAtlasRect";
75 const std::string PIXEL_AREA_UNIFORM_NAME = "pixelArea";
76 const Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f);
78 const char* VERTEX_SHADER = DALI_COMPOSE_SHADER(
79 attribute mediump vec2 aPosition;\n
80 uniform mediump mat4 uMvpMatrix;\n
81 uniform mediump vec3 uSize;\n
82 uniform mediump vec4 uAtlasRect;\n
83 uniform mediump vec4 pixelArea;
84 varying mediump vec2 vTexCoord;\n
88 mediump vec4 vertexPosition = vec4(aPosition, 0.0, 1.0);\n
89 vertexPosition.xyz *= uSize;\n
90 vertexPosition = uMvpMatrix * vertexPosition;\n
92 vTexCoord = mix( uAtlasRect.xy, uAtlasRect.zw, pixelArea.xy+pixelArea.zw*(aPosition + vec2(0.5) ) );\n
93 gl_Position = vertexPosition;\n
97 const char* FRAGMENT_SHADER = DALI_COMPOSE_SHADER(
98 varying mediump vec2 vTexCoord;\n
99 uniform sampler2D sTexture;\n
100 uniform lowp vec4 uColor;\n
104 gl_FragColor = texture2D( sTexture, vTexCoord ) * uColor;\n
108 Geometry GenerateGeometry( const Vector< Vector2 >& vertices, const Vector< unsigned int >& indices )
110 Property::Map vertexFormat;
111 vertexFormat[ "aPosition" ] = Property::VECTOR2;
112 PropertyBuffer vertexPropertyBuffer = PropertyBuffer::New( vertexFormat, vertices.Size() );
113 if( vertices.Size() > 0 )
115 vertexPropertyBuffer.SetData( &vertices[ 0 ] );
118 Property::Map indexFormat;
119 indexFormat[ "indices" ] = Property::INTEGER;
120 PropertyBuffer indexPropertyBuffer = PropertyBuffer::New( indexFormat, indices.Size() );
121 if( indices.Size() > 0 )
123 indexPropertyBuffer.SetData( &indices[ 0 ] );
126 // Create the geometry object
127 Geometry geometry = Geometry::New();
128 geometry.AddVertexBuffer( vertexPropertyBuffer );
129 geometry.SetIndexBuffer( indexPropertyBuffer );
130 geometry.SetGeometryType( Geometry::TRIANGLE_STRIP );
135 Geometry CreateGeometry( RendererFactoryCache& factoryCache, ImageDimensions gridSize )
139 if( gridSize == ImageDimensions( 1, 1 ) )
141 geometry = factoryCache.GetGeometry( RendererFactoryCache::QUAD_GEOMETRY );
144 geometry = factoryCache.CreateQuadGeometry();
145 factoryCache.SaveGeometry( RendererFactoryCache::QUAD_GEOMETRY, geometry );
150 uint16_t gridWidth = gridSize.GetWidth();
151 uint16_t gridHeight = gridSize.GetHeight();
154 Vector< Vector2 > vertices;
155 vertices.Reserve( ( gridWidth + 1 ) * ( gridHeight + 1 ) );
157 for( int y = 0; y < gridHeight + 1; ++y )
159 for( int x = 0; x < gridWidth + 1; ++x )
161 vertices.PushBack( Vector2( (float)x/gridWidth - 0.5f, (float)y/gridHeight - 0.5f) );
166 Vector< unsigned int > indices;
167 indices.Reserve( (gridWidth+2)*gridHeight*2 - 2);
169 for( unsigned int row = 0u; row < gridHeight; ++row )
171 unsigned int rowStartIndex = row*(gridWidth+1u);
172 unsigned int nextRowStartIndex = rowStartIndex + gridWidth +1u;
174 if( row != 0u ) // degenerate index on non-first row
176 indices.PushBack( rowStartIndex );
179 for( unsigned int column = 0u; column < gridWidth+1u; column++) // main strip
181 indices.PushBack( rowStartIndex + column);
182 indices.PushBack( nextRowStartIndex + column);
185 if( row != gridHeight-1u ) // degenerate index on non-last row
187 indices.PushBack( nextRowStartIndex + gridWidth );
191 return GenerateGeometry( vertices, indices );
197 } //unnamed namespace
199 ImageRenderer::ImageRenderer( RendererFactoryCache& factoryCache, ImageAtlasManager& atlasManager )
200 : ControlRenderer( factoryCache ),
201 mAtlasManager( atlasManager ),
203 mFittingMode( FittingMode::DEFAULT ),
204 mSamplingMode( SamplingMode::DEFAULT )
208 ImageRenderer::~ImageRenderer()
212 void ImageRenderer::DoInitialize( Actor& actor, const Property::Map& propertyMap )
214 std::string oldImageUrl = mImageUrl;
216 Property::Value* imageURLValue = propertyMap.Find( IMAGE_URL_NAME );
219 imageURLValue->Get( mImageUrl );
220 if( !mImageUrl.empty() )
225 Property::Value* fittingValue = propertyMap.Find( IMAGE_FITTING_MODE );
229 fittingValue->Get( fitting );
231 mFittingMode = FittingMode::DEFAULT;
232 if( fitting == SHRINK_TO_FIT )
234 mFittingMode = FittingMode::SHRINK_TO_FIT;
236 else if( fitting == SCALE_TO_FILL )
238 mFittingMode = FittingMode::SCALE_TO_FILL;
240 else if( fitting == FIT_WIDTH )
242 mFittingMode = FittingMode::FIT_WIDTH;
244 else if( fitting == FIT_HEIGHT )
246 mFittingMode = FittingMode::FIT_HEIGHT;
248 else if( fitting == DEFAULT )
250 mFittingMode = FittingMode::DEFAULT;
254 DALI_ASSERT_ALWAYS("Unknown fitting mode");
258 Property::Value* samplingValue = propertyMap.Find( IMAGE_SAMPLING_MODE );
261 std::string sampling;
262 samplingValue->Get( sampling );
264 mSamplingMode = SamplingMode::DEFAULT;
265 if( sampling == BOX )
267 mSamplingMode = SamplingMode::BOX;
269 else if( sampling == NEAREST )
271 mSamplingMode = SamplingMode::NEAREST;
273 else if( sampling == LINEAR )
275 mSamplingMode = SamplingMode::LINEAR;
277 else if( sampling == BOX_THEN_NEAREST )
279 mSamplingMode = SamplingMode::BOX_THEN_NEAREST;
281 else if( sampling == BOX_THEN_LINEAR )
283 mSamplingMode = SamplingMode::BOX_THEN_LINEAR;
285 else if( sampling == NO_FILTER )
287 mSamplingMode = SamplingMode::NO_FILTER;
289 else if( sampling == DONT_CARE )
291 mSamplingMode = SamplingMode::DONT_CARE;
293 else if( sampling == DEFAULT )
295 mSamplingMode = SamplingMode::DEFAULT;
299 DALI_ASSERT_ALWAYS("Unknown sampling mode");
303 int desiredWidth = 0;
304 Property::Value* desiredWidthValue = propertyMap.Find( IMAGE_DESIRED_WIDTH );
305 if( desiredWidthValue )
307 desiredWidthValue->Get( desiredWidth );
310 int desiredHeight = 0;
311 Property::Value* desiredHeightValue = propertyMap.Find( IMAGE_DESIRED_HEIGHT );
312 if( desiredHeightValue )
314 desiredHeightValue->Get( desiredHeight );
317 mDesiredSize = ImageDimensions( desiredWidth, desiredHeight );
320 // remove old renderer if exit
321 if( mImpl->mRenderer )
323 if( actor ) //remove old renderer from actor
325 actor.RemoveRenderer( mImpl->mRenderer );
327 if( !oldImageUrl.empty() ) //clean old renderer from cache
329 CleanCache( oldImageUrl );
333 // if actor is on stage, create new renderer and apply to actor
334 if( actor && actor.OnStage() )
340 void ImageRenderer::SetSize( const Vector2& size )
342 ControlRenderer::SetSize( size );
345 void ImageRenderer::GetNaturalSize( Vector2& naturalSize ) const
349 naturalSize.x = mImage.GetWidth();
350 naturalSize.y = mImage.GetHeight();
353 else if( mDesiredSize.GetWidth()>0 && mDesiredSize.GetHeight()>0)
355 naturalSize.x = mDesiredSize.GetWidth();
356 naturalSize.y = mDesiredSize.GetHeight();
359 else if( !mImageUrl.empty() )
361 ImageDimensions dimentions = ResourceImage::GetImageSize( mImageUrl );
362 naturalSize.x = dimentions.GetWidth();
363 naturalSize.y = dimentions.GetHeight();
367 naturalSize = Vector2::ZERO;
370 void ImageRenderer::SetClipRect( const Rect<int>& clipRect )
372 ControlRenderer::SetClipRect( clipRect );
375 void ImageRenderer::SetOffset( const Vector2& offset )
379 Renderer ImageRenderer::CreateRenderer() const
384 if( !mImpl->mCustomShader )
386 geometry = CreateGeometry( mFactoryCache, ImageDimensions( 1, 1 ) );
387 shader = GetImageShader(mFactoryCache);
391 geometry = CreateGeometry( mFactoryCache, mImpl->mCustomShader->mGridSize );
392 if( mImpl->mCustomShader->mVertexShader.empty() && mImpl->mCustomShader->mFragmentShader.empty() )
394 shader = GetImageShader(mFactoryCache);
398 shader = Shader::New( mImpl->mCustomShader->mVertexShader.empty() ? VERTEX_SHADER : mImpl->mCustomShader->mVertexShader,
399 mImpl->mCustomShader->mFragmentShader.empty() ? FRAGMENT_SHADER : mImpl->mCustomShader->mFragmentShader,
400 mImpl->mCustomShader->mHints );
401 if( mImpl->mCustomShader->mVertexShader.empty() )
403 shader.RegisterProperty( ATLAS_RECT_UNIFORM_NAME, FULL_TEXTURE_RECT );
404 shader.RegisterProperty( PIXEL_AREA_UNIFORM_NAME, FULL_TEXTURE_RECT );
409 Material material = Material::New( shader );
410 return Renderer::New( geometry, material );
413 void ImageRenderer::InitializeRenderer( const std::string& imageUrl )
415 if( imageUrl.empty() )
420 mImageUrl = imageUrl;
421 mImpl->mRenderer.Reset();
423 if( !mImpl->mCustomShader &&
424 ( strncasecmp( imageUrl.c_str(), HTTP_URL, sizeof(HTTP_URL) -1 ) != 0 ) && // ignore remote images
425 ( strncasecmp( imageUrl.c_str(), HTTPS_URL, sizeof(HTTPS_URL) -1 ) != 0 ) )
427 mImpl->mRenderer = mFactoryCache.GetRenderer( imageUrl );
428 if( !mImpl->mRenderer )
431 Material material = mAtlasManager.Add(atlasRect, imageUrl, mDesiredSize, mFittingMode, mSamplingMode );
434 Geometry geometry = CreateGeometry( mFactoryCache, ImageDimensions( 1, 1 ) );
435 mImpl->mRenderer = Renderer::New( geometry, material );
436 mImpl->mRenderer.RegisterProperty( ATLAS_RECT_UNIFORM_NAME, atlasRect );
438 else // big image, atlasing is not applied
440 mImpl->mRenderer = CreateRenderer();
442 ResourceImage image = Dali::ResourceImage::New( imageUrl, mDesiredSize, mFittingMode, mSamplingMode );
443 image.LoadingFinishedSignal().Connect( this, &ImageRenderer::OnImageLoaded );
444 Material material = mImpl->mRenderer.GetMaterial();
445 material.AddTexture( image, TEXTURE_UNIFORM_NAME );
448 mFactoryCache.SaveRenderer( imageUrl, mImpl->mRenderer );
451 mImpl->mFlags |= Impl::IS_FROM_CACHE;
455 // for custom shader or remote image, renderer is not cached and atlas is not applied
457 mImpl->mFlags &= ~Impl::IS_FROM_CACHE;
458 mImpl->mRenderer = CreateRenderer();
459 ResourceImage resourceImage = Dali::ResourceImage::New( imageUrl, mDesiredSize, mFittingMode, mSamplingMode );
460 resourceImage.LoadingFinishedSignal().Connect( this, &ImageRenderer::OnImageLoaded );
461 ApplyImageToSampler( resourceImage );
465 void ImageRenderer::InitializeRenderer( const Image& image )
467 mImpl->mFlags &= ~Impl::IS_FROM_CACHE;
469 mImpl->mRenderer = CreateRenderer();
473 ApplyImageToSampler( image );
478 void ImageRenderer::DoSetOnStage( Actor& actor )
480 if( !mImageUrl.empty() )
482 InitializeRenderer( mImageUrl );
486 InitializeRenderer( mImage );
490 void ImageRenderer::DoSetOffStage( Actor& actor )
492 //If we own the image then make sure we release it when we go off stage
493 if( !mImageUrl.empty() )
495 actor.RemoveRenderer( mImpl->mRenderer );
496 CleanCache(mImageUrl);
502 actor.RemoveRenderer( mImpl->mRenderer );
503 mImpl->mRenderer.Reset();
507 void ImageRenderer::DoCreatePropertyMap( Property::Map& map ) const
510 map.Insert( RENDERER_TYPE, RENDERER_TYPE_VALUE );
511 if( !mImageUrl.empty() )
513 map.Insert( IMAGE_URL_NAME, mImageUrl );
514 map.Insert( IMAGE_DESIRED_WIDTH, mDesiredSize.GetWidth() );
515 map.Insert( IMAGE_DESIRED_HEIGHT, mDesiredSize.GetHeight() );
519 map.Insert( IMAGE_DESIRED_WIDTH, static_cast<int>(mImage.GetWidth()) );
520 map.Insert( IMAGE_DESIRED_HEIGHT, static_cast<int>(mImage.GetHeight()) );
522 ResourceImage resourceImage = ResourceImage::DownCast(mImage);
525 map.Insert( IMAGE_URL_NAME, resourceImage.GetUrl() );
529 switch( mFittingMode )
531 case Dali::FittingMode::FIT_HEIGHT:
533 map.Insert( IMAGE_FITTING_MODE, FIT_HEIGHT );
536 case Dali::FittingMode::FIT_WIDTH:
538 map.Insert( IMAGE_FITTING_MODE, FIT_WIDTH );
541 case Dali::FittingMode::SCALE_TO_FILL:
543 map.Insert( IMAGE_FITTING_MODE, SCALE_TO_FILL );
546 case Dali::FittingMode::SHRINK_TO_FIT:
548 map.Insert( IMAGE_FITTING_MODE, SHRINK_TO_FIT );
553 map.Insert( IMAGE_FITTING_MODE, DEFAULT );
558 switch( mSamplingMode )
560 case Dali::SamplingMode::BOX:
562 map.Insert( IMAGE_SAMPLING_MODE, BOX );
565 case Dali::SamplingMode::NEAREST:
567 map.Insert( IMAGE_SAMPLING_MODE, NEAREST );
570 case Dali::SamplingMode::LINEAR:
572 map.Insert( IMAGE_SAMPLING_MODE, LINEAR );
575 case Dali::SamplingMode::BOX_THEN_LINEAR:
577 map.Insert( IMAGE_SAMPLING_MODE, BOX_THEN_LINEAR );
580 case Dali::SamplingMode::BOX_THEN_NEAREST:
582 map.Insert( IMAGE_SAMPLING_MODE, BOX_THEN_NEAREST );
585 case Dali::SamplingMode::NO_FILTER:
587 map.Insert( IMAGE_SAMPLING_MODE, NO_FILTER );
590 case Dali::SamplingMode::DONT_CARE:
592 map.Insert( IMAGE_SAMPLING_MODE, DONT_CARE );
597 map.Insert( IMAGE_SAMPLING_MODE, DEFAULT );
603 Shader ImageRenderer::GetImageShader( RendererFactoryCache& factoryCache )
605 Shader shader = factoryCache.GetShader( RendererFactoryCache::IMAGE_SHADER );
608 shader = Shader::New( VERTEX_SHADER, FRAGMENT_SHADER );
609 factoryCache.SaveShader( RendererFactoryCache::IMAGE_SHADER, shader );
610 shader.RegisterProperty( ATLAS_RECT_UNIFORM_NAME, FULL_TEXTURE_RECT );
611 shader.RegisterProperty( PIXEL_AREA_UNIFORM_NAME, FULL_TEXTURE_RECT );
616 void ImageRenderer::SetImage( Actor& actor, const std::string& imageUrl, ImageDimensions size, Dali::FittingMode::Type fittingMode, Dali::SamplingMode::Type samplingMode )
618 if( mImageUrl != imageUrl )
620 std::string oldImageUrl = mImageUrl;
621 mImageUrl = imageUrl;
623 mFittingMode = fittingMode;
624 mSamplingMode = samplingMode;
627 if( mImpl->mRenderer )
629 if( GetIsFromCache() ) // if renderer is from cache, remove the old one
631 //remove old renderer
634 actor.RemoveRenderer( mImpl->mRenderer );
638 if( !oldImageUrl.empty() )
640 CleanCache(oldImageUrl);
643 if( actor && actor.OnStage() ) // if actor on stage, create a new renderer and apply to actor
648 else // if renderer is not from cache, reuse the same renderer and only change the texture
650 ResourceImage image = Dali::ResourceImage::New( imageUrl, mDesiredSize, mFittingMode, mSamplingMode );
651 image.LoadingFinishedSignal().Connect( this, &ImageRenderer::OnImageLoaded );
652 ApplyImageToSampler( image );
658 void ImageRenderer::SetImage( Actor& actor, const Image& image )
660 if( mImage != image )
664 if( mImpl->mRenderer )
666 if( GetIsFromCache() ) // if renderer is from cache, remove the old one
668 //remove old renderer
671 actor.RemoveRenderer( mImpl->mRenderer );
675 if( !mImageUrl.empty() )
677 CleanCache(mImageUrl);
681 if( actor && actor.OnStage() ) // if actor on stage, create a new renderer and apply to actor
686 else // if renderer is not from cache, reuse the same renderer and only change the texture
688 ApplyImageToSampler( image );
693 mDesiredSize = ImageDimensions();
694 mFittingMode = FittingMode::DEFAULT;
695 mSamplingMode = SamplingMode::DEFAULT;
699 void ImageRenderer::ApplyImageToSampler( const Image& image )
703 Material material = mImpl->mRenderer.GetMaterial();
706 int index = material.GetTextureIndex( TEXTURE_UNIFORM_NAME );
709 material.SetTextureImage( index, image );
713 material.AddTexture( image, TEXTURE_UNIFORM_NAME );
718 void ImageRenderer::OnImageLoaded( ResourceImage image )
720 if( image.GetLoadingState() == Dali::ResourceLoadingFailed )
722 Image brokenImage = RendererFactory::GetBrokenRendererImage();
723 if( mImpl->mRenderer )
725 ApplyImageToSampler( brokenImage );
730 void ImageRenderer::CleanCache(const std::string& url)
732 Material material = mImpl->mRenderer.GetMaterial();
734 Vector4 atlasRect( 0.f, 0.f, 1.f, 1.f );
735 Property::Index index = mImpl->mRenderer.GetPropertyIndex( ATLAS_RECT_UNIFORM_NAME );
736 if( index != Property::INVALID_INDEX )
738 Property::Value atlasRectValue = mImpl->mRenderer.GetProperty( index );
739 atlasRectValue.Get( atlasRect );
742 mImpl->mRenderer.Reset();
743 if( mFactoryCache.CleanRendererCache( url ) && index != Property::INVALID_INDEX )
745 mAtlasManager.Remove( material, atlasRect );
749 } // namespace Internal
751 } // namespace Toolkit