Skip to content

Commit

Permalink
Support for Layer TintColor in TiledMap (libgdx#7076)
Browse files Browse the repository at this point in the history
* Added in Ability to load in the tintcolor attribute from a tiled tmx file. This will replicate the layer tinting found in tiled.
Added Opacity on a Group Parent Layer to affect child layers as this was not implemented previously, it now replicates what tiled does, same goes for the new tint color working on group layers.
There was a typo in one of the comments in the Color class. I cleaned it up and removed the typo.

* Added Test TiledMapLayerTintOpacityTest for the new tintcolor layer. And modified a missed piece of code in OrthoCachedTiledMapRenderer

* Apply formatter

* Changed background color of map test to reflect default tiled background color.

* Added tinting code to all the renderImageLayer() to specifically work as seen in the TiledMap Editor.

Slight refactoring of tinting related code in MapLayer.

Updated rendering code to use the combined Tint Color of the layer and it's parent to match what is seen in the TiledMap Editor.

Modified all previous tinting test maps to include image layers.
Added one new test map which includes a bunch of nested parent groups.

* Apply formatter

* Added method to check if image contained in TiledMapImageLayer supports transparency or not based on if it contains a png or gif (since they are supported by TiledMap Editor.
Modified render logic based on transparency support of image format.
Added small png badlogic logo to testmaps.

* Apply formatter

* Created 2 methods in BatchTiledMapRenderer specifically related to calculating the render colors for ImageLayers and TileLayers. This code was duplicated throughout 5 Map renderers. This should help clean it up and give a single point of entry for calculating colors for the map renderer's that extend the BatchTiledMapRenderer.

Refactored methods meant to check for transparency support in the TiledMapImageLayer. Now we directly check against the image texture's pixel format to determine whether this image can properly handle alpha values in a tint color.

Created static helper method in BaseTmxMapLoader meant to be used to translate TiledMap color formats to the libGDX equivalent. This cuts down on code duplication and is available for use elsewhere if needed.

Removed redundant 1 values from OrthoCachedTiledMapRenderer Color multiplication.

Small changes to 3 of the test maps, (image positions, etc)

* Apply formatter

* Moved tiled color utility method to BaseTiledMapLoader.
Updated BaseTmjMapLoader to parse tint colors to be in parity with the BaseTmxMapLoader.

* Apply formatter

---------

Co-authored-by: GitHub Action <[email protected]>
  • Loading branch information
BoBIsHere86 and actions-user authored Dec 3, 2024
1 parent 569d2c7 commit 0dc2774
Show file tree
Hide file tree
Showing 23 changed files with 1,231 additions and 21 deletions.
4 changes: 2 additions & 2 deletions gdx/src/com/badlogic/gdx/graphics/Color.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public Color set (Color color) {
}

/** Sets this color to the red, green and blue components of the provided Color and a deviating alpha value.
*
*
* @param rgb the desired red, green and blue values (alpha of that Color is ignored)
* @param alpha the desired alpha value (will be clamped to the range [0, 1])
* @return this color. */
Expand All @@ -128,7 +128,7 @@ public Color set (Color rgb, float alpha) {
return this;
}

/** Multiplies the this color and the given color
/** Multiplies this color and the given color
*
* @param color the color
* @return this color. */
Expand Down
29 changes: 28 additions & 1 deletion gdx/src/com/badlogic/gdx/maps/MapLayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@

package com.badlogic.gdx.maps;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.utils.GdxRuntimeException;

/** Map layer containing a set of objects and properties */
public class MapLayer {
private String name = "";
private float opacity = 1.0f;
private Color tintColor = new Color(Color.WHITE);
private Color tempColor = new Color(Color.WHITE);
private boolean visible = true;
private float offsetX;
private float offsetY;
Expand All @@ -46,14 +49,38 @@ public void setName (String name) {

/** @return layer's opacity */
public float getOpacity () {
return opacity;
if (parent != null)
return opacity * parent.getOpacity();
else
return opacity;
}

/** @param opacity new opacity for the layer */
public void setOpacity (float opacity) {
this.opacity = opacity;
}

/** Returns a temporary color that is the combination of this layer's tint color and its parent's tint color. The returned
* color is reused internally, so it should not be held onto or modified.
* @return layer's tint color combined with the parent's tint color */
public Color getCombinedTintColor () {
if (parent != null) {
return tempColor.set(tintColor).mul(parent.getCombinedTintColor());
} else {
return tempColor.set(tintColor);
}
}

/** @return layer's tint color */
public Color getTintColor () {
return tintColor;
}

/** @param tintColor new tint color for the layer */
public void setTintColor (Color tintColor) {
this.tintColor.set(tintColor);
}

/** @return layer's x offset */
public float getOffsetX () {
return offsetX;
Expand Down
18 changes: 14 additions & 4 deletions gdx/src/com/badlogic/gdx/maps/tiled/BaseTiledMapLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,8 @@ protected Object castProperty (String name, String value, String type) {
} else if (type.equals("bool")) {
return Boolean.valueOf(value);
} else if (type.equals("color")) {
// Tiled uses the format #AARRGGBB
String opaqueColor = value.substring(3);
String alpha = value.substring(1, 3);
return Color.valueOf(opaqueColor + alpha);
// return color after converting from #AARRGGBB to #RRGGBBAA
return Color.valueOf(tiledColorToLibGDXColor(value));
} else {
throw new GdxRuntimeException(
"Wrong type given for property " + name + ", given : " + type + ", supported : string, bool, int, float, color");
Expand Down Expand Up @@ -162,4 +160,16 @@ protected void addStaticTiledMapTile (TiledMapTileSet tileSet, TextureRegion tex
tileSet.putTile(tileId, tile);
}

/** Converts Tiled's color format #AARRGGBB to a libGDX appropriate #RRGGBBAA The Tiled Map Editor uses the color format
* #AARRGGBB But note, if the alpha of the color is set to 255, Tiled does not include it as part of the color code in the .tmx
* ex. Red (r:255,g:0,b:0,a:255) becomes #ff0000, Red (r:255,g:0,b:0,a:127) becomes #7fff0000
*
* @param tiledColor A String representing a color in Tiled's #AARRGGBB format
* @return A String representing the color in the #RRGGBBAA format */
public static String tiledColorToLibGDXColor (String tiledColor) {
String alpha = tiledColor.length() == 9 ? tiledColor.substring(1, 3) : "ff";
String color = tiledColor.length() == 9 ? tiledColor.substring(3) : tiledColor.substring(1);
return color + alpha;
}

}
5 changes: 5 additions & 0 deletions gdx/src/com/badlogic/gdx/maps/tiled/BaseTmjMapLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.badlogic.gdx.assets.loaders.FileHandleResolver;
import com.badlogic.gdx.assets.loaders.TextureLoader;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.maps.*;
import com.badlogic.gdx.maps.objects.EllipseMapObject;
Expand Down Expand Up @@ -300,6 +301,7 @@ protected void loadImageLayer (TiledMap map, MapLayers parentLayers, JsonValue e
protected void loadBasicLayerInfo (MapLayer layer, JsonValue element) {
String name = element.getString("name");
float opacity = element.getFloat("opacity", 1.0f);
String tintColor = element.getString("tintcolor", "#ffffffff");
boolean visible = element.getBoolean("visible", true);
float offsetX = element.getFloat("offsetx", 0);
float offsetY = element.getFloat("offsety", 0);
Expand All @@ -313,6 +315,9 @@ protected void loadBasicLayerInfo (MapLayer layer, JsonValue element) {
layer.setOffsetY(offsetY);
layer.setParallaxX(parallaxX);
layer.setParallaxY(parallaxY);

// set layer tint color after converting from #AARRGGBB to #RRGGBBAA
layer.setTintColor(Color.valueOf(tiledColorToLibGDXColor(tintColor)));
}

protected void loadObject (TiledMap map, MapLayer layer, JsonValue element) {
Expand Down
5 changes: 5 additions & 0 deletions gdx/src/com/badlogic/gdx/maps/tiled/BaseTmxMapLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.badlogic.gdx.assets.loaders.FileHandleResolver;
import com.badlogic.gdx.assets.loaders.TextureLoader;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.maps.*;
import com.badlogic.gdx.maps.objects.EllipseMapObject;
Expand Down Expand Up @@ -307,6 +308,7 @@ protected void loadImageLayer (TiledMap map, MapLayers parentLayers, Element ele
protected void loadBasicLayerInfo (MapLayer layer, Element element) {
String name = element.getAttribute("name", null);
float opacity = Float.parseFloat(element.getAttribute("opacity", "1.0"));
String tintColor = element.getAttribute("tintcolor", "#ffffffff");
boolean visible = element.getIntAttribute("visible", 1) == 1;
float offsetX = element.getFloatAttribute("offsetx", 0);
float offsetY = element.getFloatAttribute("offsety", 0);
Expand All @@ -320,6 +322,9 @@ protected void loadBasicLayerInfo (MapLayer layer, Element element) {
layer.setOffsetY(offsetY);
layer.setParallaxX(parallaxX);
layer.setParallaxY(parallaxY);

// set layer tint color after converting from #AARRGGBB to #RRGGBBAA
layer.setTintColor(Color.valueOf(tiledColorToLibGDXColor(tintColor)));
}

protected void loadObject (TiledMap map, MapLayer layer, Element element) {
Expand Down
30 changes: 30 additions & 0 deletions gdx/src/com/badlogic/gdx/maps/tiled/TiledMapImageLayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.badlogic.gdx.maps.tiled;

import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.maps.MapLayer;

Expand All @@ -27,13 +28,42 @@ public class TiledMapImageLayer extends MapLayer {
private float y;
private boolean repeatX;
private boolean repeatY;
private boolean supportsTransparency;

public TiledMapImageLayer (TextureRegion region, float x, float y, boolean repeatX, boolean repeatY) {
this.region = region;
this.x = x;
this.y = y;
this.repeatX = repeatX;
this.repeatY = repeatY;
this.supportsTransparency = checkTransparencySupport(region);
}

/** TiledMap ImageLayers can support transparency through tint color if the image provided supports the proper pixel format.
* Here we check to see if the file supports transparency by checking the format of the TextureData.
*
* @param region TextureRegion of the ImageLayer
* @return boolean */
private boolean checkTransparencySupport (TextureRegion region) {
Pixmap.Format format = region.getTexture().getTextureData().getFormat();
return format != null && formatHasAlpha(format);
}

// Check if pixel format supports alpha channel
private boolean formatHasAlpha (Pixmap.Format format) {
switch (format) {
case Alpha:
case LuminanceAlpha:
case RGBA4444:
case RGBA8888:
return true;
default:
return false;
}
}

public boolean supportsTransparency () {
return supportsTransparency;
}

public TextureRegion getTextureRegion () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,7 @@
import com.badlogic.gdx.maps.MapLayer;
import com.badlogic.gdx.maps.MapLayers;
import com.badlogic.gdx.maps.MapObject;
import com.badlogic.gdx.maps.tiled.TiledMap;
import com.badlogic.gdx.maps.tiled.TiledMapImageLayer;
import com.badlogic.gdx.maps.tiled.TiledMapRenderer;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
import com.badlogic.gdx.maps.tiled.*;
import com.badlogic.gdx.maps.tiled.tiles.AnimatedTiledMapTile;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Rectangle;
Expand Down Expand Up @@ -186,7 +183,8 @@ public void renderObject (MapObject object) {
@Override
public void renderImageLayer (TiledMapImageLayer layer) {
final Color batchColor = batch.getColor();
final float color = Color.toFloatBits(batchColor.r, batchColor.g, batchColor.b, batchColor.a * layer.getOpacity());

final float color = getImageLayerColor(layer, batchColor);

final float[] vertices = this.vertices;

Expand Down Expand Up @@ -309,6 +307,43 @@ public void renderImageLayer (TiledMapImageLayer layer) {
}
}

/** Calculates the float color for rendering an image layer, taking into account the layer's tint color, opacity, and whether
* the image format supports transparency then multiplying is against the batchColor
*
* @param layer The layer to render.
* @param batchColor The current color of the batch.
* @return The float color value to use for rendering. */
protected float getImageLayerColor (TiledMapImageLayer layer, Color batchColor) {

final Color combinedTint = layer.getCombinedTintColor();

// Check if layer supports transparency
boolean supportsTransparency = layer.supportsTransparency();

// If the Image Layer supports transparency we do not want to modify the combined tint during rendering
// and if the Image Layer does not support transparency, we want to multiply the combined tint values, by its alpha
float alphaMultiplier = supportsTransparency ? 1f : combinedTint.a;
// Only modify opacity by combinedTint.b if Image Layer supports transparency
float opacityMultiplier = supportsTransparency ? combinedTint.a : 1f;

// For image layer rendering multiply all by alpha
// except for opacity when image layer does not support transparency
return Color.toFloatBits(batchColor.r * (combinedTint.r * alphaMultiplier),
batchColor.g * (combinedTint.g * alphaMultiplier), batchColor.b * (combinedTint.b * alphaMultiplier),
batchColor.a * (layer.getOpacity() * opacityMultiplier));
}

/** Calculates the float color for rendering a tile layer, taking into account the layer's tint color and opacity, then
* multiplying is against the batchColor
*
* @param layer
* @param batchColor
* @return */
protected float getTileLayerColor (TiledMapTileLayer layer, Color batchColor) {
return Color.toFloatBits(batchColor.r * layer.getCombinedTintColor().r, batchColor.g * layer.getCombinedTintColor().g,
batchColor.b * layer.getCombinedTintColor().b, batchColor.a * layer.getCombinedTintColor().a * layer.getOpacity());
}

/** Called before the rendering of all layers starts. */
protected void beginRender () {
AnimatedTiledMapTile.updateAnimationBaseTime();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ private void init (TiledMap map) {
@Override
public void renderTileLayer (TiledMapTileLayer layer) {
final Color batchColor = batch.getColor();
final float color = Color.toFloatBits(batchColor.r, batchColor.g, batchColor.b, batchColor.a * layer.getOpacity());
final float color = getTileLayerColor(layer, batchColor);

final int layerWidth = layer.getWidth();
final int layerHeight = layer.getHeight();
Expand Down Expand Up @@ -263,7 +263,8 @@ private void renderCell (final TiledMapTileLayer.Cell cell, final float x, final
@Override
public void renderImageLayer (TiledMapImageLayer layer) {
final Color batchColor = batch.getColor();
final float color = Color.toFloatBits(batchColor.r, batchColor.g, batchColor.b, batchColor.a * layer.getOpacity());

final float color = getImageLayerColor(layer, batchColor);

final float[] vertices = this.vertices;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public IsometricStaggeredTiledMapRenderer (TiledMap map, float unitScale, Batch
@Override
public void renderTileLayer (TiledMapTileLayer layer) {
final Color batchColor = batch.getColor();
final float color = Color.toFloatBits(batchColor.r, batchColor.g, batchColor.b, batchColor.a * layer.getOpacity());
final float color = getTileLayerColor(layer, batchColor);

final int layerWidth = layer.getWidth();
final int layerHeight = layer.getHeight();
Expand Down Expand Up @@ -192,7 +192,8 @@ public void renderTileLayer (TiledMapTileLayer layer) {
@Override
public void renderImageLayer (TiledMapImageLayer layer) {
final Color batchColor = batch.getColor();
final float color = Color.toFloatBits(batchColor.r, batchColor.g, batchColor.b, batchColor.a * layer.getOpacity());

final float color = getImageLayerColor(layer, batchColor);

final float[] vertices = this.vertices;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ private Vector3 translateScreenToIso (Vector2 vec) {
@Override
public void renderTileLayer (TiledMapTileLayer layer) {
final Color batchColor = batch.getColor();
final float color = Color.toFloatBits(batchColor.r, batchColor.g, batchColor.b, batchColor.a * layer.getOpacity());
final float color = getTileLayerColor(layer, batchColor);

float tileWidth = layer.getTileWidth() * unitScale;
float tileHeight = layer.getTileHeight() * unitScale;
Expand Down Expand Up @@ -236,7 +236,8 @@ public void renderTileLayer (TiledMapTileLayer layer) {
@Override
public void renderImageLayer (TiledMapImageLayer layer) {
final Color batchColor = batch.getColor();
final float color = Color.toFloatBits(batchColor.r, batchColor.g, batchColor.b, batchColor.a * layer.getOpacity());

final float color = getImageLayerColor(layer, batchColor);

final float[] vertices = this.vertices;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,9 @@ public void renderObject (MapObject object) {

@Override
public void renderTileLayer (TiledMapTileLayer layer) {
final float color = Color.toFloatBits(1, 1, 1, layer.getOpacity());

final float color = Color.toFloatBits(layer.getCombinedTintColor().r, layer.getCombinedTintColor().g,
layer.getCombinedTintColor().b, layer.getOpacity() * layer.getCombinedTintColor().a);

final int layerWidth = layer.getWidth();
final int layerHeight = layer.getHeight();
Expand Down Expand Up @@ -359,7 +361,22 @@ public void renderTileLayer (TiledMapTileLayer layer) {

@Override
public void renderImageLayer (TiledMapImageLayer layer) {
final float color = Color.toFloatBits(1.0f, 1.0f, 1.0f, layer.getOpacity());

final Color combinedTint = layer.getCombinedTintColor();
// Check if layer supports transparency
boolean supportsTransparency = layer.supportsTransparency();

// If the Image Layer supports transparency we do not want to modify the combined tint during rendering
// and if the Image Layer does not support transparency, we want to multiply the combined tint values, by its alpha
float alphaMultiplier = supportsTransparency ? 1f : combinedTint.a;
// Only modify opacity by combinedTint.b if Image Layer supports transparency
float opacityMultiplier = supportsTransparency ? combinedTint.a : 1f;

// For image layer rendering multiply all by alpha
// except for opacity when image layer does not support transparency
final float color = Color.toFloatBits(combinedTint.r * alphaMultiplier, combinedTint.g * alphaMultiplier,
combinedTint.b * alphaMultiplier, layer.getOpacity() * opacityMultiplier);

final float[] vertices = this.vertices;

TextureRegion region = layer.getTextureRegion();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public OrthogonalTiledMapRenderer (TiledMap map, float unitScale, Batch batch) {
@Override
public void renderTileLayer (TiledMapTileLayer layer) {
final Color batchColor = batch.getColor();
final float color = Color.toFloatBits(batchColor.r, batchColor.g, batchColor.b, batchColor.a * layer.getOpacity());
final float color = getTileLayerColor(layer, batchColor);

final int layerWidth = layer.getWidth();
final int layerHeight = layer.getHeight();
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 0dc2774

Please sign in to comment.