Applying Reflection to DropShadow Effect in JavaFX - java

Using JavaFX, I am trying to draw text onto a canvas with a drop shadow and a reflection effect chained together.
The following code will display red text that is reflected, and then a drop shadow is applied to the original and reflected text.
Canvas canvas = new Canvas(400,400);
GraphicsContext context = canvas.getGraphicsContext2D();
context.setFont( new Font("Arial Bold", 48) );
context.setFill(Color.RED);
DropShadow shadow = new DropShadow(6, 2, 2, Color.BLACK);
Reflection reflect = new Reflection(10, 1.0, 1.0, 0.0);
shadow.setInput(reflect);
context.setEffect(shadow);
context.fillText("Hello, world!", 100,100);
However, the drop shadow appears "backwards" in the reflection, because the shadow needs to be applied first for a realistic effect. I tried to accomplish this by reversing the order in which the effects are applied, by changing the setInput and setEffect lines of code from above as follows:
reflect.setInput(shadow);
context.setEffect(reflect);
However, the result is that only the reflection is applied; I can not see any drop shadow at all.
Why isn't the drop shadow being applied / not visible?
How can I rewrite this code to achieve the desired effect (only using composition of effects, if possible)?

I don't know if what you say can be achieved by using GraphicsContext with standard API, so other answers are welcome. However, this might be a temporary workaround, provided that it is actually what you needed. The shadow is applied first and then the image is copied pixel by pixel to imitate the reflection effect. Please see the screenshot below: (original - on the left, new - on the right).
Full working example is attached below. It requires some tweaking to get a general solution but there should be enough to start with.
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.effect.DropShadow;
import javafx.scene.effect.Effect;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class FXApp extends Application {
private Parent createContent() {
Canvas canvas = new Canvas(400,400);
Font font = new Font("Arial Bold", 48);
Color fill = Color.RED;
DropShadow shadow = new DropShadow(6, 2, 2, Color.BLACK);
fillTextWithReflection(canvas.getGraphicsContext2D(), "Hello, world!", 100, 100, font, fill, shadow);
return new Pane(canvas);
}
private void fillTextWithReflection(GraphicsContext g, String text, double textX, double textY, Font font, Color fill, Effect effect) {
Text t = new Text(text);
t.setFont(font);
// 5 px margin
Canvas tmpCanvas = new Canvas(t.getLayoutBounds().getWidth() + 5, t.getLayoutBounds().getHeight() + 5);
// set configuration
GraphicsContext tmpContext = tmpCanvas.getGraphicsContext2D();
tmpContext.setFont(font);
tmpContext.setFill(fill);
tmpContext.setEffect(effect);
// draw on temporary context
tmpContext.fillText(text, 0, font.getSize());
// take a snapshot of the text
WritableImage snapshot = tmpCanvas.snapshot(null, null);
int w = (int)snapshot.getWidth();
int h = (int)snapshot.getHeight();
WritableImage reflected = new WritableImage(w, h);
// make an 'inverted' copy
for (int y = 0; y < h; y++) {
// imitate fading out of reflection
double alpha = y / (h - 1.0);
for (int x = 0; x < w; x++) {
Color oldColor = snapshot.getPixelReader().getColor(x, y);
Color newColor = Color.color(oldColor.getRed(), oldColor.getGreen(), oldColor.getBlue(), alpha);
reflected.getPixelWriter().setColor(x, h - 1 - y, newColor);
}
}
// draw on the actual context
// images are drawn from x, y top-left but text is filled from x, y + h
// hence corrections
// this can be replaced with actual fillText() call if required
g.drawImage(snapshot, textX, textY - font.getSize());
g.drawImage(reflected, textX, textY + h - font.getSize());
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.show();
}
}

Related

Why changing translate property of one Shape in JavaFX Group affects other Shapes after changing Scale of Group

I am working on a program in which I am learning about zooming and moving objects in JavaFX. For my program I am using Group and in the Group I have two Rectangle and I have some Sliders to change properties of both Rectangle.
Problem that occurred is that when I change translateX or translateY of the Rectangle in the program, keeping the ScaleX and ScaleY of parent Group other than 1, it also changes the position of another Rectangle but when the scale is 1 this problem does not occur.
Here is my program:
In my program I have two Rectagle called a and b and I am using Slider called aTranslateX & aTranslateY and bTranslateX & bTranslateY to change translateX and translateY of Rectagles a and b respectively. And I am using another Slider called scale to change scaleX & scaleY of Group called group which contains a and b. Again the problem does not occur when I change translate properties of rectangles a and b keeping scale(scaleX & scaleY) of the group but when I change their(of rectangles a and b) translate properties keeping scale other than one changing translate property of one rectangle also changes position of another rectangle in opposite direction of change.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Example extends Application{
private Group group;
public static void main(String... arguments){launch(arguments);}
#Override public void start(Stage primaryStage){
Rectangle a = new Rectangle(200, 200, Color.WHITE);
Rectangle b = new Rectangle(200, 200, Color.SKYBLUE);
a.setStroke(Color.BLACK);
b.setStroke(Color.MAGENTA);
group = new Group(a, b);
Slider aTranslateX = new Slider(-1600, 1600, 20);
Slider aTranslateY = new Slider(-1600, 1600, 20);
Slider bTranslateX = new Slider(-1600, 1600, 20);
Slider bTranslateY = new Slider(-1600, 1600, 20);
Slider scale = new Slider(0, 12, 1);
aTranslateX.valueProperty().addListener((o, l, c) -> {
if(c != null) b.setTranslateX(c.doubleValue());
});
aTranslateY.valueProperty().addListener((o, l, c) -> {
if(c != null) b.setTranslateY(c.doubleValue());
});
bTranslateX.valueProperty().addListener((o, l, c) -> {
if(c != null) a.setTranslateX(c.doubleValue());
});
bTranslateY.valueProperty().addListener((o, l, c) -> {
if(c != null) a.setTranslateY(c.doubleValue());
});
scale.valueProperty().addListener((o, l, c) -> {
if(c != null){
group.setScaleX(c.doubleValue());
group.setScaleY(c.doubleValue());
}
});
aTranslateX.setMinWidth(200);
aTranslateY.setMinWidth(200);
bTranslateX.setMinWidth(200);
bTranslateY.setMinWidth(200);
scale.setMinWidth(200);
Label[] labels = new Label[5];
for(int x = 0; x < labels.length; x++){
labels[x] = new Label();
labels[x].setMinWidth(150);
}
labels[0].setText("Rectangle A Translate X: ");
labels[1].setText("Rectangle A Translate Y: ");
labels[2].setText("Rectangle B Translate X: ");
labels[3].setText("Rectangle B Translate Y: ");
labels[4].setText("Scale of Group: ");
VBox top = new VBox(10,
new HBox(8, labels[0], aTranslateX, labels[1], aTranslateY),
new HBox(8, labels[2], bTranslateX, labels[3], bTranslateY),
new HBox(8, labels[4], scale));
top.setPadding(new Insets(12));
top.setStyle("-fx-background-color: #fefefe");
Pane pane = new Pane(group);
BorderPane root = new BorderPane(pane);
root.setTop(top);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setWidth(1280);
primaryStage.setHeight(700);
primaryStage.show();
}
}
How to resolve this problem or what is the correct way to change translate property of any object when scale of group is not default?
Group includes the transformations of the children in the calculation of it's size. Since the pivot point for scaling via scaleX/scaleY properties is the center of the node, changing the translate properties can cause the position of the pivot point to change resulting in movement of all children of the group. If you don't want this either use a Pane (doesn't include transforms in size calculation) instead of the Group or use a Scale transform with (0,0) as pivot point instead of the scaleX/scaleY properties:
Scale groupScale = new Scale();
group.getTransforms().add(groupScale);
groupScale.xProperty().bind(scale.valueProperty());
groupScale.yProperty().bind(scale.valueProperty());

Drawing a dynamic transparent image over another Image using SWT Graphics

I'm attempting to draw a transparent mask of combined shapes over the top of an already in place image. I have provided an example of the techniques I'm trying via the dialog code included in this post. Here's a screenshot of what it produces.
Example 1 (top left) highlights the problem I want to solve, I wish to have the 2 circles Or any intersecting shapes/arcs, all draw together with the same level of alpha, ie without the compounding opaqueness caused by drawing over the top of each other.
Example 3 (bottom left) is my attempt to resolve the issue by creating a separate image with solid shapes on, then making that entire image transparent, what happens i think is that using this technique makes an image where the White is treated as the transparent colour, so the edge of the circle is blended with white so that when you draw it on it causes a "halo" effect around the shape.
Example 2 (top left) highlights this issue further by drawing the circles in the image as transparent too, so you can see the more pink colour caused by the highlight.
My question is, without any knowledge of the background colour, and without turning anti-aliasing off, how can I achieve the effect I am trying for? Is there a way, because all my research is coming up blank? Maybe I need to use a different image drawing solution and port back to SWT? I know it's capable of drawing Transparent images if loaded directly from a file so I know it can hold this sort of data, but how do I create it?
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class FMLDialog extends Dialog
{
private Color red;
private Color blue;
public FMLDialog(Shell parentShell)
{
super(parentShell);
}
#Override
protected void configureShell(Shell shell)
{
red = new Color(shell.getDisplay(), new RGB(255,0,0));
blue = new Color(shell.getDisplay(), new RGB(0,100,255));
super.configureShell(shell);
shell.setSize(new Point(450,550));
shell.setText("FML");
}
#Override
public Control createDialogArea(final Composite comp)
{
Composite content = (Composite) super.createDialogArea(comp);
Composite parent = new Composite(content, SWT.NONE);
GridLayout gridLayout2 = new GridLayout(1, false);
parent.setLayout(gridLayout2);
parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
final Canvas c = new Canvas(parent, SWT.BORDER);
c.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
c.addPaintListener(new PaintListener() {
#Override
public void paintControl(PaintEvent e) {
e.gc.setAntialias(SWT.ON);
drawFirstLayer(e.gc, 0, 0);
drawFirstLayer(e.gc, 210, 0);
drawFirstLayer(e.gc, 210, 210);
drawFirstLayer(e.gc, 0, 210);
drawSecondLayerTake1(e.gc, 0, 0);
drawSecondLayerTake2(e.gc, 210, 0);
drawSecondLayerTake3(e.gc, 0, 210);
drawSecondLayerTake4(e.gc, 210, 210);
}
});
return content;
}
private void drawFirstLayer(GC gc, int x, int y) {
gc.setBackground(blue);
gc.fillOval(x, y, 200 , 200);
}
private void drawSecondLayerTake1(GC gc, int x, int y) {
// Simply draw 2 transparent circles
// Issue here is the overlap between circles where the Alpha layers up
gc.setAlpha(100);
gc.setBackground(red);
gc.fillOval(x + 70, y + 70, 60 , 60);
gc.fillOval(x + 100, y + 100, 60 , 60);
gc.setAlpha(255);
}
private void drawSecondLayerTake2(GC gc, int x, int y) {
// Create an image with 2 transparent circles
// Issue here is the overlap between circles where the Alpha layers up from the first
// PLUS becasue my transparent colour is fixed to white the alpa on the circles is blended in to the white
final Image src = new Image(null, 300, 300);
final ImageData imageData = src.getImageData();
imageData.transparentPixel = imageData.getPixel(0, 0);
src.dispose();
final Image processedImage = new Image(Display.getCurrent(), imageData);
final GC imageGC = new GC(processedImage);
imageGC.setAntialias(SWT.ON);
imageGC.setAlpha(100);
imageGC.setBackground(red);
imageGC.fillOval(70, 70, 60 , 60);
imageGC.fillOval(100, 100, 60 , 60);
imageGC.dispose();
gc.drawImage(processedImage, x + 0, y + 0);
}
private void drawSecondLayerTake3(GC gc, int x, int y) {
// Create an image with 2 solid circles, then draw that image on to the canvas with Alpha values.
// Overlap issue goes away because the whole image is being made transparent together HOWEVER
// there is a Halo effect around the edge of the red where the original circles were antialiased to blend into the "white"
// background.
final Image src = new Image(null, 300, 300);
final ImageData imageData = src.getImageData();
imageData.transparentPixel = imageData.getPixel(0, 0);
src.dispose();
final Image processedImage = new Image(Display.getCurrent(), imageData);
final GC imageGC = new GC(processedImage);
imageGC.setAntialias(SWT.ON);
imageGC.setBackground(red);
imageGC.fillOval(70, 70, 60 , 60);
imageGC.fillOval(100, 100, 60 , 60);
imageGC.dispose();
gc.setAlpha(100);
gc.drawImage(processedImage, x + 0, y + 0);
}
private void drawSecondLayerTake4(GC gc, int x, int y) {
// I need this one to draw like take 3 but without the white "halo" effect on the edge
// How?!
}
public static void main(String[] args) {
Display d = new Display();
Shell s = new Shell();
FMLDialog fml = new FMLDialog(s);
fml.open();
}
}
I was able to get the desired result using the method described by Sean Bright here: https://stackoverflow.com/a/15685473/6245535.
Basically:
we create an image src and with gc we fill it with transparent color
we draw the ovals with solid color
we get the resulting image data: now, the pixel data array of the image (imageData.data) is also going to contain the alpha values, while the alpha data array of the image (imageData.alphaData) is null
we manually fix imageData.alphaData by extracting the alpha values at the right positions from imageData.data; this part assumes that we are working with 32 bit depth of color; it won't work otherwise
now that the alphaData of imageData is fixed, we create an image processedImage with it
with gc we finally draw processedImage with partial transparency
Here's the code (which is Sean's code with some changes):
private void drawSecondLayerTake4(GC gc, int x, int y) {
final int width = 300;
final int height = 300;
final Image src = new Image(null, width, height);
final GC imageGC = new GC(src);
imageGC.setAntialias(SWT.ON);
// This sets the alpha on the entire canvas to transparent
imageGC.setAlpha(0);
imageGC.fillRectangle(0, 0, width, height);
// Reset our alpha and draw the ovals
imageGC.setAlpha(255);
imageGC.setBackground(red);
imageGC.fillOval(70, 70, 60, 60);
imageGC.fillOval(100, 100, 60, 60);
// We're done with the GC, so dispose of it
imageGC.dispose();
final ImageData imageData = src.getImageData();
imageData.alphaData = new byte[width * height];
// This is the hacky bit that is making assumptions about
// the underlying ImageData. In my case it is 32 bit data
// so every 4th byte in the data array is the alpha for that
// pixel...
for (int idx = 0; idx < (width * height); idx++) {
final int coord = (idx * 4) + 3;
imageData.alphaData[idx] = imageData.data[coord];
}
// Now that we've set the alphaData, we can create our
// final image
final Image processedImage = new Image(Display.getCurrent(), imageData);
gc.setAlpha(100);
gc.drawImage(processedImage, x + 0, y + 0);
// And get rid of the canvas
src.dispose();
}
And here's the result:
You can use a Path to merge the 2 circles in a single entity and then fill it with the transparent color.
It is a much simpler solution than my previous answer and there is no halo effect.
The code:
private void drawSecondLayerTake4(GC gc, int x, int y) {
final Path path = new Path(Display.getCurrent());
path.addArc(x + 70, y + 70, 60, 60, 0, 360);
path.addArc(x + 100, y + 100, 60, 60, 0, 360);
gc.setAlpha(100);
gc.setBackground(red);
// needed to avoid holes in the path
gc.setFillRule(SWT.FILL_WINDING);
gc.fillPath(path);
path.dispose();
}
And the result:

How to draw an 1 pixel line using Javafx Canvas?

I've been googling and searching, found some some related questions/posts but none of the address my problem.
I am drawing lines directly on canvas (JavaFX) using:
gc.setStroke(color);
gc.setLineWidth(lineWidth);
gc.strokeLine(startX, startY, endX, endY);
I want 1 pixel width lines. So I set lineWidth=1.
I get this:
Note that the lines are blurred. It is not 1 pixel.
I've tried to set lineWidth to 0.1 or 0.01, etc. It does not change the result.
By the way... I do not understand why this parameter is a double. I read somewhere that it has to do with DPI. But I do not understand what is the unit and how it is converted to pixels.
Oracle's documentation does not help. (or I did not find the one that helps)
I'd like to get this instead:
This was implemented in another platform. Note that lines are sharp and have just one 1 pixel.
Imagine each pixel as a (small) rectangle (instead of a point). The integer coordinates are the boundaries between pixels; so a (horizontal or vertical) line with integer coordinates falls "between pixels". This is rendered via antialising, approximating half of the line on one pixel and half on the other. Moving the line 0.5 pixels left or right moves it to the center of the pixel, getting around the issue.
Here's a sample:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class SharpCanvasTest extends Application {
#Override
public void start(Stage primaryStage) {
Canvas sharpCanvas = createCanvasGrid(600, 300, true);
Canvas blurryCanvas = createCanvasGrid(600, 300, false);
VBox root = new VBox(5, sharpCanvas, blurryCanvas);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private Canvas createCanvasGrid(int width, int height, boolean sharp) {
Canvas canvas = new Canvas(width, height);
GraphicsContext gc = canvas.getGraphicsContext2D() ;
gc.setLineWidth(1.0);
for (double x = sharp ? 0.5 : 0.0; x < width; x+=10) {
gc.moveTo(x, 0);
gc.lineTo(x, height);
gc.stroke();
}
for (double y = sharp ? 0.5 : 0.0; y < height; y+=10) {
gc.moveTo(0, y);
gc.lineTo(width, y);
gc.stroke();
}
return canvas ;
}
public static void main(String[] args) {
launch(args);
}
}
And the results:
Use coordinates in this notation x.5.
Look my example:
gc.setFill(Color.BLACK);
gc.setLineWidth(1.0);
gc.strokeRect(50, 100, 25.0, 25.0);
gc.strokeRect(100.5, 100.5, 25.0, 25.0);
You will get two squares, the second sharp.
Reference: https://dlsc.com/2014/04/10/javafx-tip-2-sharp-drawing-with-canvas-api/

Android TextView Long Shadow

I've been trying this since morning, yet I can't get it to work.
What I'm trying to do is create a somewhat like long shadow for the TextView, which is similar to the following:
http://www.iceflowstudios.com/v3/wp-content/uploads/2013/07/long_shadow_banner.jpg
http://web3canvas.com/wp-content/uploads/2013/07/lsd-ps-action-720x400.png
My solution so far was to create a lot of TextViews and cascade them under each other, but there are a lot of performance issues if I go with the current way.
Another solution is the usage of a custom font that has that similar allure, yet I cannot find any that matches the font I am currently using.
So I was wondering, is it possible to use: (I have to mention, the textviews are created dynamically)
TV.setShadowLayer(1f, 5f, 5f, Color.GREY);
To create several of them in a line (as a cascading layer), making the shadow seem smooth? Or do you guys suggest any other solutions?
Thanks in advance.
Try to play with raster images:
Detect bounds of text using Paint.getTextBounds() method
Create transparent Bitmap with such metrics (W + H) x H (you may use Bitmap.Config.ALPHA_8 to optimize memory usage)
Draw text on this Bitmap at 0x0 position
Copy first row of Bitmap into new one with original width, but with height of 1px
Iterate over the Y-axis of Bitmap (from top to bottom) and draw single-line Bitmap with the corresponding offset by X-axis (you will overdraw some transparent pixels)
Now you have the top-part of your shadow
Draw the bottom part using same technique, but choosing last row of this Bitmap
This algorithm may be optimized if you detect, that all pixels in last row have the same color (full shadow).
UPDATE 1
I achieved such result using this quick solution:
MainActivity.java
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle state) {
super.onCreate(state);
LongShadowTextView longShadow = new LongShadowTextView(this);
longShadow.setText("Hello World");
setContentView(longShadow);
}
}
LongShadowTextView.java
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.View;
public class LongShadowTextView extends View {
private Bitmap mBitmap;
private String mText;
public LongShadowTextView(Context context) {
super(context);
}
public void setText(String text) {
Paint paint = new Paint();
// TODO provide setters for these values
paint.setColor(Color.BLACK);
paint.setTextSize(142);
Rect rect = new Rect();
paint.getTextBounds(text, 0, text.length(), rect);
Bitmap bitmap = Bitmap.createBitmap(rect.width() + rect.height(), rect.height(), Bitmap.Config.ALPHA_8);
Canvas canvas = new Canvas(bitmap);
canvas.drawText(text, 0, rect.height(), paint);
Rect src = new Rect();
RectF dst = new RectF();
int w = bitmap.getWidth();
int h = bitmap.getHeight();
src.left = 0;
src.right = w;
for (int i = 0; i < h; ++i) {
src.top = i;
src.bottom = i + 1;
dst.left = 1;
dst.top = i + 1;
dst.right = 1 + w;
dst.bottom = i + 2;
canvas.drawBitmap(bitmap, src, dst, null);
}
mText = text;
mBitmap = bitmap;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mBitmap, 0, 0, null);
}
}
UPDATE 2
Here is final result which I achieved. Clone this demo from github.
I'm afraid your suggested approach of using setShadowLayer() wont work as this approach effectively draws a second TextPaint with blurring.
Superimposing several TextPaints on top of each other will essentially mean you need to offset it by 1px for each step, which is very graphically intensive and will have a very poor performance.
This is an excellent question and a real challenge!
The only solution that comes to mind is to handle each glyph independently, inspecting all path elements and extending a shadow between the furthest bottom-left and top-right point. This seems very complicated, and I don't know if there's any mechanics in the SDK that facilitates an approach like that.
Suggested reading:
This question tackles obtaining glyph paths from TTFs.
This answer illustrates how you can leverage using paths, although it concerns a JavaScript approach.
Small comment if someone would try to run setText() method. it is not working now.
You should call invalidate(); in setText(); method
public void setText(String value) {
boolean changed =
mText == null && value != null || mText != null && !mText.equals(value);
mText = value;
if (changed) {
refresh();
}
invalidate();
}

ShapeRenderer produces pixelated shapes using LibGDX

When I use a ShapeRenderer, it always comes out pixelated. But if I draw the shape in photoshop with the same dimensions, it's very smooth and clean-looking.
My method is just as follows:
package com.me.actors;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.scenes.scene2d.Actor;
public class bub_actors extends Actor {
private ShapeRenderer shapes;
private Texture text;
private Sprite sprite;
public bub_actors(){
shapes = new ShapeRenderer();
text = new Texture(Gdx.files.internal("data/circle.png"));
sprite = new Sprite();
sprite.setRegion(text);
}
#Override
public void draw(SpriteBatch batch, float parentAlpha) {
batch.draw(sprite, 200, 200, 64, 64);
shapes.begin(ShapeType.FilledCircle);
shapes.filledCircle(50, 50, 32);
shapes.setColor(Color.BLACK);
shapes.end();
}
}
Here's an image of the output:
Any ideas as to why this happens? Is it possible to make the ShapeRenderer look like the image (so I don't have to create a SpriteBatch of different-colored circles...).
The difference is anti-aliasing that Photoshop applies to the image it generates. If you zoom in on the edges of the two circles, you'll see the anti-aliased one has some semi-black pixels around the edge, where the ShapeRenderer generated circle just shows pixels entirely on or off.
The Libgdx ShapeRenderer was designed for being a quick and simple way to get debugging shapes on the screen, it does not support anti-aliasing. The easiest way to get consistent anti-aliased rendering it to use a texture. (Its also possible with an OpenGL shader.)
That said, you do not have to create different sprites just to render different colored circles. Just use a white circle with a transparent background, and then render it with a color. (Assuming you want a variety of solid-colored circles).
Here is really simple way to achieve smooth & well-looking shapes without using a texture and SpriteBatch.
All you have to do is to render couple of shapes with slightly larger size and
lower alpha channel along with the first one.
The more passes the better result, but, of course, consider ppi of your screen.
...
float alphaMultiplier = 0.5f; //you may play with different coefficients
float radiusStep = radius/200;
int sampleRate = 3;
...
//do not forget to enable blending
Gdx.gl.glEnable(GL20.GL_BLEND);
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
shapeRenderer.begin(ShapeType.Filled);
//first rendering
shapeRenderer.setColor(r, g, b, a);
shapeRenderer.circle(x, y, radius);
//additional renderings
for(int i=0; i<sampleRate; i++) {
a *= alphaMultiplier;
radius += radiusStep;
shapeRenderer.setColor(r, g, b, a);
shapeRenderer.circle(x, y, radius);
}
shapeRenderer.end();
...
Here is a screenshot of what can you achieve.
If you're not teetering on the edge of losing frames, you can enable antialiasing in your launcher. You can increase the sample count for better results, but it's really diminishing returns.
LWJGL3 : config.setBackBufferConfig(8, 8, 8, 8, 16, 0, 2);
LWJGL2 : config.samples = 2;
GWT : config.antialiasing = true;
Android: config.numSamples = 2;
iOS : config.multisample = GLKViewDrawableMultisample._4X;

Categories

Resources