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:
Related
I want to display a text within a OpenGL Android Application. I have tried the code below, but, nothing is displayed. Some other attempts have been tried but none was displayed either. Could anyone help me with some advice?
String aText = "Prueba";
float aFontSize = 100;
int[] textureId = new int[1];
Paint textPaint = new Paint();
textPaint.setTextSize(aFontSize);
textPaint.setFakeBoldText(false);
textPaint.setAntiAlias(true);
textPaint.setARGB(255, 255, 255, 255);
// If a hinting is available on the platform you are developing, you should enable it (uncomment the line below).
//textPaint.setHinting(Paint.HINTING_ON);
textPaint.setSubpixelText(true);
textPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN));
float realTextWidth = textPaint.measureText(aText);
// Creates a new mutable bitmap, with 128px of width and height
int bitmapWidth = (int)(realTextWidth + 2.0f);
int bitmapHeight = (int)aFontSize + 2;
Bitmap textBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
textBitmap.eraseColor(Color.argb(0, 255, 255, 255));
// Creates a new canvas that will draw into a bitmap instead of rendering into the screen
Canvas bitmapCanvas = new Canvas(textBitmap);
// Set start drawing position to [1, base_line_position]
// The base_line_position may vary from one font to another but it usually is equal to 75% of font size (height).
bitmapCanvas.drawText(aText, 1, 1.0f + aFontSize * 0.75f, textPaint);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[0]);
// Assigns the OpenGL texture with the Bitmap
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, textBitmap, 0);
// Free memory resources associated with this texture
textBitmap.recycle();
// After the image has been subloaded to texture, regenerate mipmaps
GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
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();
}
}
Im making a simple pong game and want to put a texture over my paddle but I don't know how to import image into my game. I tried doing in like here in 3rd example but it doesn't work:
Image myImage = getImage(getCodeBase(), "texture.png");
g.drawImage(myImage, 0, 0 , 10, 120, this);
g cannot be resolved
Here's some code:
public void run(){
Image myImage = getImage(getCodeBase(), "texture.png");
g.drawImage(myImage, 0, 0 , 10, 120, this);
GOval ball = makeBall();
add(ball);
GRect paddleLeft = makePaddle();
GRect paddleRight = makePaddle();
add(paddleLeft);
add(paddleRight);
bounce(ball, paddleLeft, paddleRight);
}
public static GRect makePaddle(){
GRect result = new GRect(0,0,WIDTH,HEIGHT);
result.setFilled(true);
result.setColor(Color.BLACK);
return result;
}
the texture.png is for the paddles
EDIT:
I got the texture to load, but I can't make it move with the paddles, I don't know why
WIDTH is width of the paddle, getWidth() - window. I guess the code I use to move the paddle should work for the texture but it doesnt
with the image.sendToFront() player's paddle's texture works, but the AI's doesn't
if(mouseY<getHeight()-HEIGHT){ // Player
paddleLeft.setLocation(WIDTH,mouseY);
image.setLocation(WIDTH,mouseY);
image.sendToFront();
}
else{
paddleLeft.setLocation(WIDTH,getHeight()-HEIGHT);
image.setLocation(WIDTH,getHeight()-HEIGHT);
image.sendToFront();
}
if(ball.getY()<getHeight()-paddleRight.getHeight()){ // AI
paddleRight.setLocation(getWidth()-2*WIDTH,ball.getY());
image2.setLocation(getWidth()-2*WIDTH,ball.getY());
image2.sendToFront();
}
else
paddleRight.setLocation(getWidth()-2*WIDTH,getHeight()-paddleRight.getHeight());
image2.setLocation(getWidth()-2*WIDTH,getHeight()-paddleRight.getHeight());
image2.sendToFront();
It seems like you are using a library from http://cs.stanford.edu/people/eroberts/jtf/ (based on the GRect class etc).
And it seems like you should be able to use a GImage as well. So the code could roughly look like
Image myImage = getImage(getCodeBase(), "texture.png");
//g.drawImage(myImage, 0, 0 , 10, 120, this);
GImage image = new GImage(myImage);
add(image);
I encountered a problem within my (very) simple project. When I draw text, it is displayed in a different position (and appareantly with different size) between Android (Samsung Galaxy S Advance) and Desktop.
In my renderer class I have:
private OrthographicCamera cam;
public final int WIDTH = 320;
public final int HEIGHT = 480;
private ShapeRenderer debugRenderer = new ShapeRenderer();
private SpriteBatch batch = new SpriteBatch();
private BitmapFont font;
And in my constructor:
MyClass(){
this.cam = new OrthographicCamera(WIDTH, HEIGHT);
this.cam.position.set(WIDTH/2, HEIGHT/2, 0);
this.cam.update();
font = new BitmapFont(Gdx.files.internal("data/fonts/font.fnt"));
font.setColor(Color.RED);
}
Finally, the render function:
public void render() {
// For each block y use this code:
// debugRenderer.begin(ShapeType.Filled);
// Rectangle rect = block.getBounds();
// debugRenderer.rect(rect.x, rect.y, rect.width, rect.height);
// debugRenderer.end();
// And then I draw my text:
batch.begin();
font.draw(batch, "Score: " + world.getScore(), 50,50)
batch.end();
}
All block dimensions are in absolute numbers (not relative to any variable like stage.getWidth() or something like that).
The result in Desktop is the following:
While in Android I have:
As you may see, blocks have the same distribution along the screen (in terms of % of screen covered), while text doesn't. It doesn't start in the same place and doesn't have the same height.
Does anybody know what I could be doing wrong?
Just in case it helps, blocks bounds are:
XPos: 45 * i
YPos: 45 * j
SizeX: 32
SizeY: 32
EDIT: Another consideration, the font I'm using is extracted from: https://github.com/libgdx/libgdx/tree/master/demos/superjumper
After this.cam.update(); do you do -
batch.setProjectionMatrix(this.cam.combined);
debugRenderer.setProjectionMatrix(this.cam.combined);
The code below plots some simple x-y data, but it has two problems that I do not know how to fix.
First, it plots negative values for some of the data points, which means lines extending southward below the x-axis. Since the data points are selected at random, you may have to resize the frame a bit in order to view new random numbers to be plotted in a way that shows this bug. All data values will be positive, so I want all deflections to project northward above the blue bottom marker line, and I need to make sure that no deflections extend southward below the blue bottom marker line.
Second, the y-axis label takes up too much real estate on the screen. It needs to be rotated -90 degrees. However, all the examples I have seen for this involve rotating the entire panel using a graphics2d object. I do not want to rotate the entire panel. Instead, I just want to rotate the text of the y-axis label.
Can anyone show me how to change the code below to fix these two specific problems?
The code is in the following two files:
GUI.java
import java.awt.*;
import javax.swing.*;
class GUI{
GUI() {
// Create a new JFrame container.
JFrame jfrm = new JFrame("X-Y Plot");
// Specify FlowLayout for the layout manager.
jfrm.getContentPane().setLayout(new FlowLayout());
int frameHeight = 400;
int frameWidth = 300;
// Give the frame an initial size.
jfrm.setSize(frameWidth, frameHeight);
// Terminate the program when the user closes the application.
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Create a text-based label.
JVertLabel myVLabel = new JVertLabel("y-axis label");
int width = myVLabel.WIDTH;
PaintPanel myPP = new PaintPanel(frameWidth-width-50-20,frameHeight-70);
jfrm.add(myPP);
jfrm.add(myVLabel);// Add the label to the frame.
// Display the frame.
jfrm.setVisible(true);
}
public static void main(String args[]) {
// Create the frame on the event dispatching thread.
SwingUtilities.invokeLater(new Runnable() {public void run(){new GUI();}});
}
public class JVertLabel extends JComponent {
private String text;
public JVertLabel(String s) {
text = s;
}//constructor
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.rotate(Math.toRadians(-90));
g2d.drawString(text, 0, 0);
}
}
}
PaintPanel.java
import java.awt.*;
import javax.swing.*;
import java.util.*;
class PaintPanel extends JPanel {
Insets ins; // holds the panel's insets
Random rand; // used to generate random numbers
PaintPanel(int w, int h) {
setOpaque(true);// Ensure that panel is opaque.
setPreferredSize(new Dimension(w, h));// Set preferred dimension as specfied.
rand = new Random();
}
protected void paintComponent(Graphics g) {// Override paintComponent() method.
super.paintComponent(g);// Always call superclass method first.
int height = getHeight();// Get height of component.
int width = getWidth();// Get width of component.
ins = getInsets();// Get the insets.
// Get dimensions of text
Graphics2D g2d = (Graphics2D) g;
Font font = new Font("Serif", Font.PLAIN, 12);
FontMetrics fontMetrics = g2d.getFontMetrics();
String xString = ("x-axis label");
int xStrWidth = fontMetrics.stringWidth(xString);
int xStrHeight = fontMetrics.getHeight();
String yString = "y-axis-label";
int yStrWidth = fontMetrics.stringWidth(yString);
int yStrHeight = fontMetrics.getHeight();
int leftStartPlotWindow = ins.left + 5 + yStrWidth;
int hPad = 3;
// Fill panel by plotting random data in a bar graph.
for (int i = leftStartPlotWindow + hPad; i <= width - leftStartPlotWindow - hPad + yStrWidth + 1; i += 4) {
int h = Math.abs(rand.nextInt(height - ins.bottom));//Get rand# betw/0 and max height of drawing area.
// If generated value w/in or too close to border, change it to just outside border.
if (h <= ins.top) {
h = ins.top + 1;
}
g.drawLine(i, Math.abs(height - ins.bottom - xStrHeight - 5), i, h);// Draw a line that represents data.
}
g.setColor(Color.blue);
g.drawRect(leftStartPlotWindow, ins.bottom + 2, width - leftStartPlotWindow - ins.right - hPad, height - xStrHeight - 6);
g.setColor(Color.red);
g.drawRect(ins.left, ins.bottom, width - ins.left - 1, height - ins.bottom - 1);
g.drawString(xString, (width / 2) - (xStrWidth / 2), height - ins.bottom - 6);
g.drawString(yString, ins.left, height / 2);
}
}
All data values will be positive, so I want all deflections to project northward above the blue bottom marker line, and I need to make sure that no deflections extend southward below the blue bottom marker line.
You need to calculate the random height so that all values fit into the space available. So the calculation would be something like:
int randomHeight = panelHeight - offset.top - offset.bottom - heightForTheXAxisText;
Then you don't have to worry about negative values or the top of the line extending outside the bounds of the panel.
all the examples I have seen for this involve rotating the entire panel using a graphics2d object. I do not want to rotate the entire panel. Instead, I just want to rotate the text of the y-axis label.
Set the rotation of the Graphics object, the draw the text, then restore the rotation of the Graphics object back to 0.
Or, you create create a new Graphcis object from the current Graphics object, then apply the rotation, draw the text and then dispose of the temporaray Graphics object.
JFreeChart addresses both issues by default, as shown in this example.
In your example,
You'll have to create the data model before trying to render it. Then you can scan it for min and max to determine the limits of your range axis. List<Double> may be a suitable choice.
You can rotate the range label by altering the graphics context's AffineTransform, as shown in RotateText.