Browse Source

New keywords in image decorator for controlling scaling and centering, see demo sample for examples. #54.

Michael Ragazzon 6 years ago
parent
commit
e46b7be077

+ 54 - 2
Samples/basic/demo/data/demo.rml

@@ -69,7 +69,7 @@ panel
 button:focus {
 button:focus {
 	image-color: #0ff;
 	image-color: #0ff;
 }
 }
-#decorators div {
+#decorators > div {
 	margin-bottom: 14px;
 	margin-bottom: 14px;
 }
 }
 #decorators p {
 #decorators p {
@@ -109,6 +109,36 @@ button:focus {
 {
 {
 	border-color: #F9EFA3;
 	border-color: #F9EFA3;
 }
 }
+.image-fit { text-align: center; }
+.image-fit > div
+{
+	display: inline-block;
+	width: 120px;
+	height: 50px;
+	padding: 5px 10px;
+	margin-right: 20px;
+	background-color: #c665;
+	border: 1px #666;
+	text-align: left;
+}
+.image-fit.small > div
+{
+	width: 40px;
+	height: 20px;
+	padding: 0;
+	margin-left: 40px;
+	margin-right: 80px;
+}
+.fit-fill       { decorator: image( icon-invader fill ); }
+.fit-contain    { decorator: image( icon-invader contain ); }
+.fit-cover      { decorator: image( icon-invader cover ); }
+.fit-center     { decorator: image( icon-invader center ); }
+.fit-scale-down { decorator: image( icon-invader scale-down ); }
+.transform.fit-fill       { decorator: image( icon-invader fill        flip-horizontal ); }
+.transform.fit-contain    { decorator: image( icon-invader contain     flip-horizontal ); }
+.transform.fit-cover      { decorator: image( icon-invader cover       flip-horizontal ); }
+.transform.fit-center     { decorator: image( icon-invader center      flip-horizontal ); }
+.transform.fit-scale-down { decorator: image( icon-invader scale-down  flip-horizontal ); }
 p.emojis
 p.emojis
 {
 {
 	text-align: left;
 	text-align: left;
@@ -141,7 +171,7 @@ textarea {
 		<button>Image</button>
 		<button>Image</button>
 		<button class="ninepatch">Ninepatch</button>
 		<button class="ninepatch">Ninepatch</button>
 	</div>
 	</div>
-	<div>	
+	<div>
 		<button class="big">Image</button>
 		<button class="big">Image</button>
 		<button class="big ninepatch">Ninepatch</button>
 		<button class="big ninepatch">Ninepatch</button>
 		<p>The ninepatch decorator scales the background without blurring the texture, keeping the corners fixed.</p>
 		<p>The ninepatch decorator scales the background without blurring the texture, keeping the corners fixed.</p>
@@ -150,6 +180,28 @@ textarea {
 		<button class="gradient">Gradient</button>
 		<button class="gradient">Gradient</button>
 		<button class="gradient horizontal">Gradient</button>
 		<button class="gradient horizontal">Gradient</button>
 	</div>
 	</div>
+	<h1>Image decorator fit modes</h1>
+	<div class="image-fit">
+		<div class="fit-fill">fill</div>
+		<div class="fit-contain">contain</div>
+		<div class="fit-cover">cover</div>
+		<div class="fit-center">center</div>
+		<div class="fit-scale-down">scale-down</div>
+	</div>
+	<div class="image-fit small">
+		<div class="fit-fill"></div>
+		<div class="fit-contain"></div>
+		<div class="fit-cover"></div>
+		<div class="fit-center"></div>
+		<div class="fit-scale-down"></div>
+	</div>
+	<div class="image-fit small">
+		<div class="transform fit-fill"></div>
+		<div class="transform fit-contain"></div>
+		<div class="transform fit-cover"></div>
+		<div class="transform fit-center"></div>
+		<div class="transform fit-scale-down"></div>
+	</div>
 </panel>
 </panel>
 <tab>Buttons</tab>
 <tab>Buttons</tab>
 <panel>
 <panel>

+ 124 - 48
Source/Core/DecoratorTiled.cpp

@@ -42,7 +42,7 @@ DecoratorTiled::~DecoratorTiled()
 {
 {
 }
 }
 
 
-static Vector2f oriented_texcoords[6][2] = {{Vector2f(0, 0), Vector2f(1, 1)},
+static const Vector2f oriented_texcoords[6][2] = {{Vector2f(0, 0), Vector2f(1, 1)},
 													   {Vector2f(0, 1), Vector2f(1, 0)},
 													   {Vector2f(0, 1), Vector2f(1, 0)},
 													   {Vector2f(1, 1), Vector2f(0, 0)},
 													   {Vector2f(1, 1), Vector2f(0, 0)},
 													   {Vector2f(1, 0), Vector2f(0, 1)},
 													   {Vector2f(1, 0), Vector2f(0, 1)},
@@ -53,6 +53,7 @@ DecoratorTiled::Tile::Tile() : position(0, 0), size(1, 1)
 {
 {
 	texture_index = -1;
 	texture_index = -1;
 	repeat_mode = STRETCH;
 	repeat_mode = STRETCH;
+	fit_mode = FILL;
 	orientation = ROTATE_0;
 	orientation = ROTATE_0;
 
 
 	position_absolute[0] = false;
 	position_absolute[0] = false;
@@ -118,6 +119,9 @@ Vector2f DecoratorTiled::Tile::GetDimensions(Element* element) const
 // Generates geometry to render this tile across a surface.
 // Generates geometry to render this tile across a surface.
 void DecoratorTiled::Tile::GenerateGeometry(std::vector< Vertex >& vertices, std::vector< int >& indices, Element* element, const Vector2f& surface_origin, const Vector2f& surface_dimensions, const Vector2f& tile_dimensions) const
 void DecoratorTiled::Tile::GenerateGeometry(std::vector< Vertex >& vertices, std::vector< int >& indices, Element* element, const Vector2f& surface_origin, const Vector2f& surface_dimensions, const Vector2f& tile_dimensions) const
 {
 {
+	if (surface_dimensions.x <= 0 || surface_dimensions.y <= 0)
+		return;
+
 	RenderInterface* render_interface = element->GetRenderInterface();
 	RenderInterface* render_interface = element->GetRenderInterface();
 	const auto& computed = element->GetComputedValues();
 	const auto& computed = element->GetComputedValues();
 
 
@@ -133,68 +137,140 @@ void DecoratorTiled::Tile::GenerateGeometry(std::vector< Vertex >& vertices, std
 
 
 	const TileData& data = data_iterator->second;
 	const TileData& data = data_iterator->second;
 
 
-	int num_tiles[2];
-	Vector2f final_tile_dimensions;
+	int num_tiles[2] = { 0, 0 };
+	Vector2f final_tile_dimensions( 0, 0 );
 
 
 	// Generate the oriented texture coordinates for the tiles.
 	// Generate the oriented texture coordinates for the tiles.
 	Vector2f scaled_texcoords[3];
 	Vector2f scaled_texcoords[3];
 	for (int i = 0; i < 2; i++)
 	for (int i = 0; i < 2; i++)
 	{
 	{
-		scaled_texcoords[i].x = data.texcoords[0].x + oriented_texcoords[orientation][i].x * (data.texcoords[1].x - data.texcoords[0].x);
-		scaled_texcoords[i].y = data.texcoords[0].y + oriented_texcoords[orientation][i].y * (data.texcoords[1].y - data.texcoords[0].y);
+		scaled_texcoords[i] = data.texcoords[0] + oriented_texcoords[orientation][i] * (data.texcoords[1] - data.texcoords[0]);
 	}
 	}
 	scaled_texcoords[2] = scaled_texcoords[1];
 	scaled_texcoords[2] = scaled_texcoords[1];
 
 
 	// Resize the dimensions (if necessary) to fit this tile's repeat mode.
 	// Resize the dimensions (if necessary) to fit this tile's repeat mode.
 	for (int i = 0; i < 2; i++)
 	for (int i = 0; i < 2; i++)
 	{
 	{
-		if (surface_dimensions[i] <= 0)
-			num_tiles[i] = 0;
+		switch (repeat_mode)
+		{
+		case STRETCH:
+		{
+			// If the tile is stretched, we only need one quad.
+			num_tiles[i] = 1;
+			final_tile_dimensions[i] = surface_dimensions[i];
+		}
+		break;
+
+		case CLAMP_STRETCH:
+		case CLAMP_TRUNCATE:
+		{
+			// If the tile is clamped, we only need one quad if the surface is smaller than the tile, or two if it's larger (to take the last stretched pixel).
+			num_tiles[i] = surface_dimensions[i] > tile_dimensions[i] ? 2 : 1;
+			if (num_tiles[i] == 1)
+			{
+				final_tile_dimensions[i] = surface_dimensions[i];
+				if (repeat_mode == CLAMP_TRUNCATE)
+					scaled_texcoords[1][i] -= (scaled_texcoords[1][i] - scaled_texcoords[0][i]) * (1.0f - (final_tile_dimensions[i] / tile_dimensions[i]));
+			}
+			else
+				final_tile_dimensions[i] = surface_dimensions[i] - tile_dimensions[i];
+		}
+		break;
+
+		case REPEAT_STRETCH:
+		case REPEAT_TRUNCATE:
+		{
+			num_tiles[i] = Math::RealToInteger((surface_dimensions[i] + (tile_dimensions[i] - 1)) / tile_dimensions[i]);
+			num_tiles[i] = Math::Max(0, num_tiles[i]);
+
+			final_tile_dimensions[i] = surface_dimensions[i] - (num_tiles[i] - 1) * tile_dimensions[i];
+			if (final_tile_dimensions[i] <= 0)
+				final_tile_dimensions[i] = tile_dimensions[i];
+
+			if (repeat_mode == REPEAT_TRUNCATE)
+				scaled_texcoords[2][i] -= (scaled_texcoords[1][i] - scaled_texcoords[0][i]) * (1.0f - (final_tile_dimensions[i] / tile_dimensions[i]));
+		}
+		break;
+		}
+	}
+
+
+	Vector2f tile_offset(0, 0);
+	bool clamp_tile = false;
+	
+	// For now, we assume that fit mode and repeat mode cannot both be set on a single tile. The two code paths won't work well together.
+	RMLUI_ASSERT(repeat_mode == STRETCH || fit_mode == FILL);
+
+	switch (fit_mode)
+	{
+	case FILL:
+		// Do nothing, use results from above.
+	break;
+	case CONTAIN:
+	{
+		Vector2f scale_factor = surface_dimensions / tile_dimensions;
+		float min_factor = std::min(scale_factor.x, scale_factor.y);
+		final_tile_dimensions = tile_dimensions * min_factor;
+
+		tile_offset = ((surface_dimensions - final_tile_dimensions) * 0.5f).Round();
+	}
+	break;
+	case COVER:
+	{
+		Vector2f scale_factor = surface_dimensions / tile_dimensions;
+		float max_factor = std::max(scale_factor.x, scale_factor.y);
+		final_tile_dimensions = tile_dimensions * max_factor;
+
+		tile_offset = ((surface_dimensions - final_tile_dimensions) * 0.5f).Round();
+		clamp_tile = true;
+	}
+	break;
+	case CENTER:
+	{
+		final_tile_dimensions = tile_dimensions;
+		
+		tile_offset = ((surface_dimensions - final_tile_dimensions) * 0.5f).Round();
+		clamp_tile = true;
+	}
+	break;
+	case SCALE_DOWN:
+	{
+		Vector2f scale_factor = surface_dimensions / tile_dimensions;
+		float min_factor = std::min(scale_factor.x, scale_factor.y);
+		if (min_factor < 1.0f)
+			final_tile_dimensions = tile_dimensions * min_factor;
 		else
 		else
+			final_tile_dimensions = tile_dimensions;
+
+		tile_offset = ((surface_dimensions - final_tile_dimensions) * 0.5f).Round();
+	}
+	break;
+	}
+
+	if (clamp_tile)
+	{
+		// Along each dimension, see if our tile extends outside the boundary at either side.
+		for(int i = 0; i < 2; i++)
 		{
 		{
-			switch (repeat_mode)
+			// Left/right acts as top/bottom during the second iteration.
+			float overshoot_left = std::max(-tile_offset[i], 0.0f);
+			float overshoot_right = std::max(tile_offset[i] + final_tile_dimensions[i] - surface_dimensions[i], 0.0f);
+
+			if(overshoot_left > 0.f || overshoot_right > 0.f)
 			{
 			{
-				// If the tile is stretched, we only need one quad.
-				case STRETCH:
-				{
-					num_tiles[i] = 1;
-					final_tile_dimensions[i] = surface_dimensions[i];
-				}
-				break;
-
-				// If the tile is clamped, we only need one quad if the surface is smaller than the tile, or two if it's
-				// larger (to take the last stretched pixel).
-				case CLAMP_STRETCH:
-				case CLAMP_TRUNCATE:
-				{
-					num_tiles[i] = surface_dimensions[i] > tile_dimensions[i] ? 2 : 1;
-					if (num_tiles[i] == 1)
-					{
-						final_tile_dimensions[i] = surface_dimensions[i];
-						if (repeat_mode == CLAMP_TRUNCATE)
-							scaled_texcoords[1][i] -= (scaled_texcoords[1][i] - scaled_texcoords[0][i]) * (1.0f - (final_tile_dimensions[i] / tile_dimensions[i]));
-					}
-					else
-						final_tile_dimensions[i] = surface_dimensions[i] - tile_dimensions[i];
-				}
-				break;
-
-				case REPEAT_STRETCH:
-				case REPEAT_TRUNCATE:
-				{
-					num_tiles[i] = Math::RealToInteger((surface_dimensions[i] + (tile_dimensions[i] - 1)) / tile_dimensions[i]);
-					num_tiles[i] = Math::Max(0, num_tiles[i]);
-
-					final_tile_dimensions[i] = surface_dimensions[i] - (num_tiles[i] - 1) * tile_dimensions[i];
-					if (final_tile_dimensions[i] <= 0)
-						final_tile_dimensions[i] = tile_dimensions[i];
-
-					if (repeat_mode == REPEAT_TRUNCATE)
-						scaled_texcoords[2][i] -= (scaled_texcoords[1][i] - scaled_texcoords[0][i]) * (1.0f - (final_tile_dimensions[i] / tile_dimensions[i]));
-				}
-				break;
+				float& left = scaled_texcoords[0][i];
+				float& right = scaled_texcoords[1][i];
+				float width = right - left;
+
+				left += overshoot_left / final_tile_dimensions[i] * width;
+				right -= overshoot_right / final_tile_dimensions[i] * width;
+
+				final_tile_dimensions[i] -= overshoot_left + overshoot_right;
+				tile_offset[i] += overshoot_left;
 			}
 			}
 		}
 		}
+
+		scaled_texcoords[2] = scaled_texcoords[1];
 	}
 	}
 
 
 	// If any of the axes are zero or below, then we have a zero surface area and nothing to render.
 	// If any of the axes are zero or below, then we have a zero surface area and nothing to render.
@@ -263,7 +339,7 @@ void DecoratorTiled::Tile::GenerateGeometry(std::vector< Vertex >& vertices, std
 			tile_position.x = surface_origin.x + (float) tile_dimensions.x * x;
 			tile_position.x = surface_origin.x + (float) tile_dimensions.x * x;
 			tile_size.x = (float) (x < num_tiles[0] - 1 ? tile_dimensions.x : final_tile_dimensions.x);
 			tile_size.x = (float) (x < num_tiles[0] - 1 ? tile_dimensions.x : final_tile_dimensions.x);
 
 
-			tile_position = tile_position.Round();
+			tile_position = (tile_position + tile_offset).Round();
 			tile_size = tile_size.Round();
 			tile_size = tile_size.Round();
 
 
 			GeometryUtilities::GenerateQuad(new_vertices, new_indices, tile_position, tile_size, quad_colour, tile_texcoords[0], tile_texcoords[1], index_offset);
 			GeometryUtilities::GenerateQuad(new_vertices, new_indices, tile_position, tile_size, quad_colour, tile_texcoords[0], tile_texcoords[1], index_offset);

+ 13 - 1
Source/Core/DecoratorTiled.h

@@ -69,11 +69,22 @@ public:
 	{
 	{
 		ROTATE_0 = 0,			// Rotated zero degrees clockwise.
 		ROTATE_0 = 0,			// Rotated zero degrees clockwise.
 		ROTATE_90 = 1,			// Rotated 90 degrees clockwise.
 		ROTATE_90 = 1,			// Rotated 90 degrees clockwise.
-		ROTATE_18 = 2,			// Rotated 180 degrees clockwise.
+		ROTATE_180 = 2,			// Rotated 180 degrees clockwise.
 		ROTATE_270 = 3,			// Rotated 270 degrees clockwise.
 		ROTATE_270 = 3,			// Rotated 270 degrees clockwise.
 		FLIP_HORIZONTAL = 4,	// Flipped horizontally.
 		FLIP_HORIZONTAL = 4,	// Flipped horizontally.
 		FLIP_VERTICAL = 5		// Flipped vertically.
 		FLIP_VERTICAL = 5		// Flipped vertically.
 	};
 	};
+	/**
+		Stores the fit mode of a tile.
+	 */
+	enum TileFitMode
+	{
+		FILL,       // Tile is stretched to boundaries.
+		CONTAIN,    // Tile is stretched to boundaries, keeping aspect ratio fixed, 'letter-boxed'.
+		COVER,      // Tile is stretched to cover the boundaries, keeping aspect ratio fixed, and clipped.
+		CENTER,     // Tile is centered and never stretched, clipped if too large.
+		SCALE_DOWN, // Tile acts like 'center' if smaller than boundaries, or like 'contain' otherwise.
+	};
 
 
 	/**
 	/**
 		Structure for storing the different tiles the tiled decorator uses internally over its
 		Structure for storing the different tiles the tiled decorator uses internally over its
@@ -119,6 +130,7 @@ public:
 		mutable TileDataMap data;
 		mutable TileDataMap data;
 
 
 		TileRepeatMode repeat_mode;
 		TileRepeatMode repeat_mode;
+		TileFitMode fit_mode;
 		TileOrientation orientation;
 		TileOrientation orientation;
 	};
 	};
 
 

+ 1 - 1
Source/Core/DecoratorTiledImageInstancer.cpp

@@ -35,7 +35,7 @@ namespace Core {
 
 
 DecoratorTiledImageInstancer::DecoratorTiledImageInstancer() : DecoratorTiledInstancer(1)
 DecoratorTiledImageInstancer::DecoratorTiledImageInstancer() : DecoratorTiledInstancer(1)
 {
 {
-	RegisterTileProperty("image", false);
+	RegisterTileProperty("image", false, true);
 	RegisterShorthand("decorator", "image", ShorthandType::RecursiveRepeat);
 	RegisterShorthand("decorator", "image", ShorthandType::RecursiveRepeat);
 }
 }
 
 

+ 23 - 6
Source/Core/DecoratorTiledInstancer.cpp

@@ -39,7 +39,7 @@ DecoratorTiledInstancer::DecoratorTiledInstancer(size_t num_tiles)
 }
 }
 
 
 // Adds the property declarations for a tile.
 // Adds the property declarations for a tile.
-void DecoratorTiledInstancer::RegisterTileProperty(const String& name, bool register_repeat_modes)
+void DecoratorTiledInstancer::RegisterTileProperty(const String& name, bool register_repeat_modes, bool register_fit_modes)
 {
 {
 	TilePropertyIds ids = {};
 	TilePropertyIds ids = {};
 
 
@@ -55,19 +55,30 @@ void DecoratorTiledInstancer::RegisterTileProperty(const String& name, bool regi
 		.AddParser("keyword", "none, rotate-90, rotate-180, rotate-270, flip-horizontal, flip-vertical")
 		.AddParser("keyword", "none, rotate-90, rotate-180, rotate-270, flip-horizontal, flip-vertical")
 		.GetId();
 		.GetId();
 
 
+	String additional_modes;
+
 	if (register_repeat_modes)
 	if (register_repeat_modes)
 	{
 	{
-		ids.repeat = RegisterProperty(CreateString(32, "%s-repeat", name.c_str()), "stretch")
+		String repeat_name = CreateString(32, "%s-repeat", name.c_str());
+		ids.repeat = RegisterProperty(repeat_name, "stretch")
 			.AddParser("keyword", "stretch, clamp-stretch, clamp-truncate, repeat-stretch, repeat-truncate")
 			.AddParser("keyword", "stretch, clamp-stretch, clamp-truncate, repeat-stretch, repeat-truncate")
 			.GetId();
 			.GetId();
-		RegisterShorthand(name, CreateString(256, "%s-src, %s-repeat, %s-x, %s-y, %s-width, %s-height, %s-orientation", name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str()), ShorthandType::FallThrough);
+		additional_modes += repeat_name + ", ";
 	}
 	}
-	else
+
+	if (register_fit_modes)
 	{
 	{
-		ids.repeat = PropertyId::Invalid;
-		RegisterShorthand(name, CreateString(256, "%s-src, %s-x, %s-y, %s-width, %s-height, %s-orientation", name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str()), ShorthandType::FallThrough);
+		String fit_name = CreateString(32, "%s-fit", name.c_str());
+		ids.fit = RegisterProperty(fit_name, "fill")
+			.AddParser("keyword", "fill, contain, cover, center, scale-down")
+			.GetId();
+		additional_modes += fit_name + ", ";
 	}
 	}
 
 
+	RegisterShorthand(name, CreateString(256, ("%s-src, " + additional_modes + "%s-orientation, %s-x, %s-y, %s-width, %s-height").c_str(),
+		name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str()),
+		ShorthandType::FallThrough);
+
 	tile_property_ids.push_back(ids);
 	tile_property_ids.push_back(ids);
 }
 }
 
 
@@ -163,6 +174,12 @@ bool DecoratorTiledInstancer::GetTileProperties(DecoratorTiled::Tile* tiles, Tex
 			tile.repeat_mode = (DecoratorTiled::TileRepeatMode)repeat_property.value.Get< int >();
 			tile.repeat_mode = (DecoratorTiled::TileRepeatMode)repeat_property.value.Get< int >();
 		}
 		}
 
 
+		if (ids.fit != PropertyId::Invalid)
+		{
+			const Property& fit_property = *properties.GetProperty(ids.fit);
+			tile.fit_mode = (DecoratorTiled::TileFitMode)fit_property.value.Get< int >();
+		}
+
 		if (ids.orientation != PropertyId::Invalid)
 		if (ids.orientation != PropertyId::Invalid)
 		{
 		{
 			const Property& repeat_property = *properties.GetProperty(ids.orientation);
 			const Property& repeat_property = *properties.GetProperty(ids.orientation);

+ 3 - 2
Source/Core/DecoratorTiledInstancer.h

@@ -51,7 +51,8 @@ protected:
 	/// Adds the property declarations for a tile.
 	/// Adds the property declarations for a tile.
 	/// @param[in] name The name of the tile property.
 	/// @param[in] name The name of the tile property.
 	/// @param[in] register_repeat_modes If true, the tile will have the repeat modes registered.
 	/// @param[in] register_repeat_modes If true, the tile will have the repeat modes registered.
-	void RegisterTileProperty(const String& name, bool register_repeat_modes);
+	/// @param[in] register_fit_modes If true, the tile will have the fit modes registered.
+	void RegisterTileProperty(const String& name, bool register_repeat_modes, bool register_fit_modes = false);
 
 
 	/// Retrieves all the properties for a tile from the property dictionary.
 	/// Retrieves all the properties for a tile from the property dictionary.
 	/// @param[out] tile The tile structure for storing the tile properties.
 	/// @param[out] tile The tile structure for storing the tile properties.
@@ -62,7 +63,7 @@ protected:
 
 
 private:
 private:
 	struct TilePropertyIds {
 	struct TilePropertyIds {
-		PropertyId src, repeat, x, y, width, height, orientation;
+		PropertyId src, repeat, x, y, width, height, orientation, fit;
 	};
 	};
 
 
 	std::vector<TilePropertyIds> tile_property_ids;
 	std::vector<TilePropertyIds> tile_property_ids;