I want to create a pie chart with images in between the legend bars. I am adding the screenshot below for better understanding, i tried using one canvas and then created one arc and tried to add images to it, but it was not working. For now i am using below pie chart library to show the bars with center text. Any suggestion will be helpful. Thanks :)
https://github.com/PhilJay/MPAndroidChart
enter image description here
Regards,
Rohit Garg
You can do that by extending PieChartRenderer.
If you look at the implementation of PieChartRenderer.drawRoundedSlices(Canvas c) you can get an example of how to get the starting coordinates of each slice.
Then just use drawBitmap or drawPicture to render your image between the pie slices. (I used Utils.drawImage in the example to mimic the source of PieChartRenderer)
As an example, i copied drawRoundedSlices and renamed it drawImageBeforeSlice. Instead of drawing the arcs, i draw bitmaps.
To make the renderer use the new method, i override drawExtras and stick a call to the new method on the end.
class PieChartRendererWithImages extends PieChartRenderer
{
protected Drawable mImage;
public PieChartRendererWithImages(PieChart chart, ChartAnimator animator, ViewPortHandler viewPortHandler, Drawable image) {
super(chart, animator, viewPortHandler);
mImage = image;
}
/**
* This draws an image before all pie-slices
*
* #param c
*/
protected void drawImageBeforeSlice(Canvas c) {
IPieDataSet dataSet = mChart.getData().getDataSet();
if (!dataSet.isVisible())
return;
float phaseX = mAnimator.getPhaseX();
float phaseY = mAnimator.getPhaseY();
MPPointF center = mChart.getCenterCircleBox();
float r = mChart.getRadius();
// calculate the radius of the "slice-circle"
float circleRadius = (r - (r * mChart.getHoleRadius() / 100f)) / 2f;
float[] drawAngles = mChart.getDrawAngles();
float angle = mChart.getRotationAngle();
for (int j = 0; j < dataSet.getEntryCount(); j++) {
float sliceAngle = drawAngles[j];
Entry e = dataSet.getEntryForIndex(j);
// draw only if the value is greater than zero
if ((Math.abs(e.getY()) > Utils.FLOAT_EPSILON)) {
float x = (float) ((r - circleRadius)
* Math.cos(Math.toRadians((angle + sliceAngle)
* phaseY)) + center.x);
float y = (float) ((r - circleRadius)
* Math.sin(Math.toRadians((angle + sliceAngle)
* phaseY)) + center.y);
// draw image instead of arcs
Utils.drawImage(
c,
mImage,
(int)x,
(int)y,
mImage.getIntrinsicWidth(),
mImage.getIntrinsicHeight());
}
angle += sliceAngle * phaseX;
}
MPPointF.recycleInstance(center);
}
#Override
public void drawExtras(Canvas c) {
super.drawExtras(c);
// use drawImageBeforeSlice in last step of rendering process
drawImageBeforeSlice(c);
}
}
Don't forget to set your new renderer on your PieChart:
myPieChart.setRenderer(new PieChartRendererWithImages(myPieChart, myPieChart.getAnimator(), myPieChart.getViewPortHandler(), getResources().getDrawable(R.drawable.my_image)));
Verified to work by putting it in the MPAndroidChart example:
Related
I am clearly missing an important concept here. I have written code using mouse events to draw a boundary (a polygon) on an existing BufferedImage. Here is the relevant section:
public void paintComponent(Graphics g)
{
super.paintComponent(g); //Paint parent's background
//G3 displays the BufferedImage "Drawing" with each paint
Graphics2D G3 = (Graphics2D)g;
G3.drawImage(this.Drawing, 0, 0, null);
G3.dispose();
}
public void updateDrawing()
{
int x0, y0, x1, y1; // Vertex coordinates
Line2D.Float seg;
// grafix is painting the mouse drawing to the BufferedImage "Drawing"
if(this.pts.size() > 0)
{
for(int ip = 0; ip < pts.size(); ip++)
{
x0 = (int)this.pts.get(ip).x;
y0 = (int)this.pts.get(ip).y;
this.grafix.drawRect(x0 - this.sqw/2, y0 - this.sqh/2, + this.sqw, this.sqh);
if (ip > 0)
{
x1 = (int)this.pts.get(ip-1).x;
y1 = (int)this.pts.get(ip-1).y;
this.grafix.drawLine(x1, y1, x0, y0);
seg = new Line2D.Float(x1, y1, x0, y0);
this.segments.add(seg);
}
}
}
repaint();
}
The next two routines are called by the mouse events: Left click gets the next point and right click closes the region.
public void getNextPoint(Point2D p)
{
this.isDrawing = true;
Point2D.Float next = new Point2D.Float();
next.x = (float) p.getX();
next.y = (float) p.getY();
this.pts.add(next);
updateDrawing();
}
public void closeBoundary()
{
//Connects the last point to the first point to close the loop
Point2D.Float next = new Point2D.Float(this.pts.get(0).x, this.pts.get(0).y);
this.pts.add(next);
this.isDrawing = false;
updateDrawing();
}
It all works fine and I can save the image with my drawing on it:
image with drawing
The list of vertices (pts) and the line segments (segments) are all that describe the region/shape/polygon.
I wish to extract from the original image only that region enclosed within the boundary. That is, I plan to create a new BufferedImage by moving through all of the pixels, testing to see if they fall within the figure and keep them if they do.
So I want to create an AREA from the points and segments I've collected in drawing the shape. Everything says: create an AREA variable and "getPathIterator". But on what shape? My AREA variable will be empty. How does the path iterator access the points in my list?
I've been all over the literature and this website as well.
I'm missing something.
Thank you haraldK for your suggestion. Before I saw your post, I came to a similar conclusion:
Using the Arraylist of vertices from the paint operation, I populated a "Path2D.Float" object called "contour" by looping through the points list that was created during the "painting" operation. Using this "contour" object, I instantiated an Area called "interferogram". Just to check my work, I created another PathIterator, "PI", from the Area and decomposed the Area, "interferogram" into "segments" sending the results to the console. I show the code below:
private void mnuitmKeepInsideActionPerformed(java.awt.event.ActionEvent evt)
{
// Keeps the inner area of interest
// Vertices is the "pts" list from Class MouseDrawing (mask)
// It is already a closed path
ArrayList<Point2D.Float> vertices =
new ArrayList<>(this.mask.getVertices());
this.contour = new Path2D.Float(Path2D.WIND_NON_ZERO);
// Read the vertices into the Path2D variable "contour"
this.contour.moveTo((float)vertices.get(0).getX(),
(float)vertices.get(0).getY()); //Starting location
for(int ivertex = 1; ivertex < vertices.size(); ivertex++)
{
this.contour.lineTo((float)vertices.get(ivertex).getX(),
(float)vertices.get(ivertex).getY());
}
this.interferogram = new Area(this.contour);
PathIterator PI = this.interferogram.getPathIterator(null);
//Test print out the segment types and vertices for debug
float[] p = new float[6];
int icount = 0;
while( !PI.isDone())
{
int type = PI.currentSegment(p);
System.out.print(icount);
System.out.print(" Type " + type);
System.out.print(" X " + p[0]);
System.out.println(" Y " + p[1]);
icount++;
PI.next();
}
BufferedImage masked = Mask(this.image_out, this.interferogram);
// Write image to file for debug
String dir;
dir = System.getProperty("user.dir");
dir = dir + "\\00masked.png";
writeImage(masked, dir, "PNG");
}
Next, I applied the mask to the image testing each pixel for inclusion in the area using the code below:
public BufferedImage Mask(BufferedImage BIM, Area area)
{
/** Loop through the pixels in the image and test each one for inclusion
* within the area.
* Change the colors of those outside
**/
Point2D p = new Point2D.Double(0,0);
// rgb should be white
int rgb = (255 << 24);
for (int row = 0; row < BIM.getWidth(); row++)
{
for (int col = 0; col < BIM.getHeight(); col++)
{
p.setLocation(col, row);
if(!area.contains(p))
{
BIM.setRGB(col, row, rgb);
}
}
}
return BIM;
}
public static BufferedImage deepCopy(BufferedImage B2M)
{
ColorModel cm = B2M.getColorModel();
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
WritableRaster raster = B2M.copyData(B2M.getRaster()
.createCompatibleWritableRaster());
return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
}
This worked beautifully (I was surprised!) except for one slight detail: the lines of the area appeared around the outside of the masked image.
In order to remedy this, I copied the original (resized) image before the painting operation. Many thanks to user1050755 (Nov 2014) for the routine deepCopy that I found on this website. Applying my mask to the copied image resulted in the portion of the original image I wanted without the mask lines. The result is shown in the attached picture. I am stoked!
masked image
I have a shape and a vector of shape's points, and I want to zoom in/out (resize whatever) the shape by dragging the mouse from a point of the shape to other random point (like in windows point for example)
What I search e read is that to scale a shape you have to recalculate all coordinates to:
xScaled = firstX * scaleX
yScaled = firstY * sclaleY
My problem is how to find that scale factor, what is the formula? Remebering that I have acess to the firstHitPoint, the actual point and all the points of the shape, and I have to do this by dragging the mouse.
Here is a peaceof my code:
#Override
public void transformPoints() {
findTransformationFactors();
int size = points.size();
for(int i = 0; i < size; i++)
points.set(i, new Point(points.get(i).x * scaleX, points.get(i).y * scaleY));
}
#Override
public void findTransformationFactors() {
int oldx = firstHitPoint.x;
int oldY = firstHitPoint.y;
int actualX = actualPoint.x;
int actualY = actualPoint.y;
scaleX = ??
scaleY = ??
}
The X factor taking the center as a constant point:
xVar = (initial.getX()-mouse.getX())/(center.getX()-initial.getX());
scaleX=xVar+1.0;
Then you must control that the scale applied is not negative --> avoid mirror transform
I am drawing a hexagonal grid on my entire phone's screen. I am drawing a hexagon by drawing six lines using Canvas. Too many lines are being drawn which makes the app unresponsive. I had to do android:HardwareAccelerated=false to atleast make it work on my Nexus 4 otherwise the app crashed with this error :
06-22 14:11:46.664: A/libc(5743): Fatal signal 6 (SIGABRT) at 0x0000166f (code=-6), thread 5743 (.nadeem.sensus4)
Although the app doesn't crash now, it takes too much time to draw the grid. This is the code of my CustomView which draws the grid :
public DrawView(Context context, Hexagon hex) {
super(context);
this.context = context;
setLayerType(View.LAYER_TYPE_HARDWARE, null);
this.hex = hex;
}
#Override
public void onDraw(Canvas canvas) {
double xOff = Math.cos(Math.PI / 6) * hex.radius;//radius is 12 for now
double yOff = Math.sin(Math.PI / 6) * hex.radius; // third of the hex
// height
for (int i = 0; i < 60; ++i) {
for (int j = 0; j < 40; ++j) {
double xPos = j * xOff * 2;
if (i % 2 != 0) { // if the current line is not even
xPos += xOff; // extra offset of half the width on x axis
}
double yPos = i * yOff * 3;
createHexagon(xPos, // X pos for hexagon center on the scene
yPos, canvas);
}
}
}
public void createHexagon(double x, double y, Canvas canvas) {
paint.setColor(Color.BLACK);
paint.setStyle(Style.STROKE);
// paint.setStyle(Style.FILL);
for (int i = 0; i < 6; i++) {
double angle = 2 * Math.PI / 6 * (i + 0.5);
double x_i = x + hex.radius * Math.cos(angle);
double y_i = y + hex.radius * Math.sin(angle);
if (i == 0)
wallpath.moveTo((float) x_i, (float) y_i);
else
wallpath.lineTo((float) x_i, (float) y_i);
}
canvas.drawPath(wallpath, paint);
canvas = null;
}
I want to ask if there's way to increase the performance or any other alternate way to achieve this grid.
Do your drawing in layers. The first time you draw, draw all your hexagons to a single bitmap. Then in all future draws, just draw that bitmap to the screen. Then add in whatever else you need to draw on top of that. It will save you 14000 draw line commands.
Your other good option is to move to openGL for drawing. But there is no way you'll ever get 14K lines to draw without hardware acceleration with any real speed.
You can achieve the same effect by drawing straight lines and utilizing DashPathEffect to toggle a line from your background colour to your visible colour.
Where the faint lines don't belong is where that particular line segment would be turned off. Since the pattern is predictable, so is your stroke effect. Because you said in a comment that you also need the coordinates of all the vertices, run a separate loop that calculates from a starting point and propagate your vertices list from there.
I have a image with blackened circles.
The image is a scanned copy of an survey sheet pretty much like an OMR questionnaire sheet.
I want to detect the circles that have been blackened using the JUI(if any other api required)
I have a few examples while searching, but they dont give me accurate result.
I tried..UDAI,Moodle...etc...
Then I decided to make my own. I am able to detect the black pixels but as follows.
BufferedImage mapa = BMPDecoder.read(new File("testjui.bmp"));
final int xmin = mapa.getMinX();
final int ymin = mapa.getMinY();
final int ymax = ymin + mapa.getHeight();
final int xmax = xmin + mapa.getWidth();
for (int i = xmin;i<xmax;i++)
{
for (int j = ymin;j<ymax;j++)
{
int pixel = mapa.getRGB(i, j);
if ((pixel & 0x00FFFFFF) == 0)
{
System.out.println("("+i+","+j+")");
}
}
}
This gives me the co-ordinates of all the black pixels but i cannot make out if its a circle or not.
How can I identify if its a circle.
2] Also I want to know if the image scanned is tilted....I know that the Udai api takes care of that, but for some reason I am not able to get my survey template to run with that code.
So if I understood correctly, you have code that picks out the black pixels so now you have the coordinates of all black pixels and you want to determine all of those that fall on a circle.
The way I would approach this is in 2 steps.
1) Cluster the pixels. Create a class called Cluster, that contains a list of points and use your clustering algorithm to put all the points in the right cluster.
2) Determine which clusters are circles. To do this find the midpoint of all of the points in each cluster (just take the mean of all the points). Then find the minimum and maximum distances from the center, The difference between these should be less than the maximum thickness for a circle in your file. These will give you the radii for the innermost and outermost circles contained within the circle. Now use the equation of a circle x^2 + y^2 = radius, with the radius set to a value between the maximum and minimum found previously to find the points that your cluster should contain. If your cluster contains these it is a circle.
Of course other considerations to consider is whether the shapes you have approximate ellipses rather than circles, in which case you should use the equation of an ellipse. Furthermore, if your file contains circle-like shapes you will need to write additional code to exclude these. On the other hand if all of your circles are exactly the same size you can cut the work that needs to be done by having your algorithm search for circles of that size only.
I hope I could be of some help, good luck!
To answer your first question, I created a class that checks weather an image contains a single non black filled black outlined circle.
This class is experimental, it does not provide exact results all the time, feel free to edit it and to correct the bugs you might encounter.
The setters do not check for nulls or out of range values.
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
/**
* Checks weather an image contains a single non black filled black outlined circle<br />
* This class is experimental, it does not provide exact results all the time, feel free to edit it and to correct
* the bugs you might encounter.
* #author Ahmed KRAIEM
* #version 0.9 alpha
* #since 2013-04-03
*/
public class CircleChecker {
private BufferedImage image;
/**
* Points that are equal to the calculated radiusĀ±<code>radiusesErrorMargin%</code> are not considered rogue points.<br />
* <code>radiusesErrorMargin</code> must be <code>>0 && <1</code>
*/
private double radiusesErrorMargin = 0.2;
/**
* A shape that has fewer than roguePointSensitivity% of rogue points is considered a circle.<br />
* <code>roguePointSensitivity</code> must be <code>>0 && <1</code>
*/
private double roguePointSensitivity = 0.05;
/**
* The presumed circle is divided into <code>angleCompartimentPrecision</code> parts,<br />
* each part must have <code>minPointsPerCompartiment</code> points
* <code>angleCompartimentPrecision</code> must be <code>> 0</code>
*/
private int angleCompartimentPrecision = 50;
/**
* The minimum number of points requiered to declare a part valid.<br />
* <code>minPointsPerCompartiment</code> must be <code>> 0</code>
*/
private int minPointsPerCompartiment = 20;
public CircleChecker(BufferedImage image) {
super();
this.image = image;
}
public CircleChecker(BufferedImage image, double radiusesErrorMargin,
int minPointsPerCompartiment, double roguePointSensitivity,
int angleCompartimentPrecision) {
this(image);
this.radiusesErrorMargin = radiusesErrorMargin;
this.minPointsPerCompartiment = minPointsPerCompartiment;
this.roguePointSensitivity = roguePointSensitivity;
this.angleCompartimentPrecision = angleCompartimentPrecision;
}
public BufferedImage getImage() {
return image;
}
public void setImage(BufferedImage image) {
this.image = image;
}
public double getRadiusesErrorMargin() {
return radiusesErrorMargin;
}
public void setRadiusesErrorMargin(double radiusesErrorMargin) {
this.radiusesErrorMargin = radiusesErrorMargin;
}
public double getMinPointsPerCompartiment() {
return minPointsPerCompartiment;
}
public void setMinPointsPerCompartiment(int minPointsPerCompartiment) {
this.minPointsPerCompartiment = minPointsPerCompartiment;
}
public double getRoguePointSensitivity() {
return roguePointSensitivity;
}
public void setRoguePointSensitivity(double roguePointSensitivity) {
this.roguePointSensitivity = roguePointSensitivity;
}
public int getAngleCompartimentPrecision() {
return angleCompartimentPrecision;
}
public void setAngleCompartimentPrecision(int angleCompartimentPrecision) {
this.angleCompartimentPrecision = angleCompartimentPrecision;
}
/**
*
* #return true if the image contains no more than <code>roguePointSensitivity%</code> rogue points
* and all the parts contain at least <code>minPointsPerCompartiment</code> points.
*/
public boolean isCircle() {
List<Point> list = new ArrayList<>();
final int xmin = image.getMinX();
final int ymin = image.getMinY();
final int ymax = ymin + image.getHeight();
final int xmax = xmin + image.getWidth();
for (int i = xmin; i < xmax; i++) {
for (int j = ymin; j < ymax; j++) {
int pixel = image.getRGB(i, j);
if ((pixel & 0x00FFFFFF) == 0) {
list.add(new Point(i, j));
}
}
}
if (list.size() == 0)
return false;
double diameter = -1;
Point p1 = list.get(0);
Point across = null;
for (Point p2 : list) {
double d = distance(p1, p2);
if (d > diameter) {
diameter = d;
across = p2;
}
}
double radius = diameter / 2;
Point center = center(p1, across);
int diffs = 0;
int diffsUntilError = (int) (list.size() * roguePointSensitivity);
double minRadius = radius - radius * radiusesErrorMargin;
double maxRadius = radius + radius * radiusesErrorMargin;
int[] compartiments = new int[angleCompartimentPrecision];
for (int i=0; i<list.size(); i++) {
Point p = list.get(i);
double calRadius = distance(p, center);
if (calRadius>maxRadius || calRadius < minRadius)
diffs++;
else{
//Angle
double angle = Math.atan2(p.y -center.y,p.x-center.x);
//angle is between -pi and pi
int index = (int) ((angle + Math.PI)/(Math.PI * 2 / angleCompartimentPrecision));
compartiments[index]++;
}
if (diffs >= diffsUntilError){
return false;
}
}
int sumCompartiments = list.size() - diffs;
for(int comp : compartiments){
if (comp < minPointsPerCompartiment){
return false;
}
}
return true;
}
private double distance(Point p1, Point p2) {
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
}
private Point center(Point p1, Point p2) {
return new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
}
public static void main(String[] args) throws IOException {
BufferedImage image = ImageIO.read(new File("image.bmp"));
CircleChecker cc = new CircleChecker(image);
System.out.println(cc.isCircle());
}
}
You'll need to program in a template of what a circle would look like, and then make it scalable to suit the different circle sizes.
For example circle of radius 3 would be:
o
ooo
o
This assumes you have a finite set of circles you need to find, maybe up to 5x5 or 6x6 this would be feasible.
or you could use: Midpoint circle algorithm
This would involve finding all black pixel groups and then selecting the middle pixel for each one.
Apply this algorithm using the outer pixels as a guid to how big the circle could be.
Finding the difference between black /expected black pixels.
If the black to expected black ratio is high enough, its a black circle and you can delete / whiten it.
I am currently in the process of experimenting with a 2D tile based side scrolling game in Java, primarily based on the code and examples from "Developing Games in Java" by David Brackeen
At the moment the map files are 100x100 tiles in size (each tile is 64x64 pixels). I have already configured the system to only display the tiles which are visible to the player. The Graphics system is managed by a ScreenManager class that returns the graphics object of the current BufferStrategy as follows:
ScreenManager.java
private GraphicsDevice device;
...
/**
* Gets the graphics context for the display. The
* ScreenManager uses double buffering, so applications must
* call update() to show any graphics drawn.
* <p>
* The application must dispose of the graphics object.
*/
public Graphics2D getGraphics(){
Window window = device.getFullScreenWindow();
if(window != null){
BufferStrategy strategy = window.getBufferStrategy();
return (Graphics2D)strategy.getDrawGraphics();
}
else{
return null;
}
}
After the graphics from this ScreenManager is passed along in the game loop to the draw method of the TreeRenderer.
TreeMapRenderer.java
/**
Draws the specified TileMap.
*/
public void draw(Graphics2D g, TileMap map,
int screenWidth, int screenHeight, float fr)
{
Sprite player = map.getPlayer();
int mapWidth = tilesToPixels(map.getWidth());
int mapHeight = tilesToPixels(map.getHeight());
// get the scrolling position of the map
// based on player's position
int offsetX = screenWidth / 2 -
Math.round(player.getX()) - TILE_SIZE;
offsetX = Math.min(offsetX, 0);
offsetX = Math.max(offsetX, screenWidth - mapWidth);
// get the y offset to draw all sprites and tiles
int offsetY = screenHeight /2 -
Math.round(player.getY()) - TILE_SIZE;
offsetY = Math.min(offsetY,0);
offsetY = Math.max(offsetY, screenHeight - mapHeight);
// draw the visible tiles
int firstTileY = pixelsToTiles(-offsetY);
int lastTileY = firstTileY + pixelsToTiles(screenHeight) +1;
int firstTileX = pixelsToTiles(-offsetX);
int lastTileX = firstTileX +
pixelsToTiles(screenWidth) + 1;
//HERE IS WHERE THE SYSTEM BOGS dOWN (checking ~280 tiles per iteration)
for (int y=firstTileY; y<lastTileY; y++) {
for (int x=firstTileX; x <= lastTileX; x++) {
if(map.getTile(x, y) != null){
Image image = map.getTile(x, y).getImage();
if (image != null) {
g.drawImage(image,
tilesToPixels(x) + offsetX,
tilesToPixels(y) + offsetY,
null);
}
}
}
}
// draw player
g.drawImage(player.getImage(),
Math.round(player.getX()) + offsetX,
Math.round(player.getY()) + offsetY,
null);
The algorithm works correctly selecting the correct FROM and TO values for the X and Y axis culling the needed tiles from 10000 to ~285.
My problem is that even with this the game will only run at around 8-10 FPS while the tiles are being rendered. If I turn off tile rendering than the system runs at 80 FPS (easy to run fast when there is nothing to do)
Do you have any ideas on how to speed up this process? I would like to see something at least around the 30 FPS mark to make this playable.
And finally although I am open to using 3rd party libraries to do this I would like to try and implement this logic myself before admitting defeat.
EDIT:
As requested here is the extra information for how the call for Image image = map.getTile(x, y).getImage(); works.
The map here come from the following TileMap class
TileMap.java
public class TileMap {
private Tile[][] tiles;
private LinkedList sprites;
private Sprite player;
private GraphicsConfiguration gc;
/**
Creates a new TileMap with the specified width and
height (in number of tiles) of the map.
*/
public TileMap(GraphicsConfiguration gc, int width, int height) {
this.gc = gc;
tiles = new Tile[width][height];
overlayer = new Tile[width][height];
sprites = new LinkedList();
}
/**
Gets the width of this TileMap (number of tiles across).
*/
public int getWidth() {
return tiles.length;
}
/**
Gets the height of this TileMap (number of tiles down).
*/
public int getHeight() {
return tiles[0].length;
}
/**
Gets the tile at the specified location. Returns null if
no tile is at the location or if the location is out of
bounds.
*/
public Tile getTile(int x, int y) {
if (x < 0 || x >= getWidth() ||
y < 0 || y >= getHeight())
{
return null;
}
else {
return tiles[x][y];
}
}
/**
* Helper method to set a tile. If blocking is not defined than it is set to false.
*
* #param x
* #param y
* #param tile
*/
public void setTile(int x, int y,Image tile){
this.setTile(x,y,tile,false);
}
/**
Sets the tile at the specified location.
*/
public void setTile(int x, int y, Image tile, boolean blocking) {
if(tiles[x][y] == null){
Tile t = new Tile(gc, tile, blocking);
tiles[x][y] = t;
}
else{
tiles[x][y].addImage(tile);
tiles[x][y].setBlocking(blocking);
}
}
...
With the Tile here being an instance of the following code. Essentially this class just holds the Image which can be updated by adding an overlay layer to it always using gc.createCompatibleImage(w, h, Transparency.TRANSLUCENT); and a boolean to tell if it will block the player. The image that is passed in is also created in this manner.
Tile.java
public class Tile {
private Image image;
private boolean blocking = false;
private GraphicsConfiguration gc;
/**
* Creates a new Tile to be used with a TileMap
* #param image The base image for this Tile
* #param blocking Will this tile allow the user to walk over/through
*/
public Tile(GraphicsConfiguration gc, Image image, boolean blocking){
this.gc = gc;
this.image = image;
this.blocking = blocking;
}
public Tile(GraphicsConfiguration gc, Image image){
this.gc = gc;
this.image = image;
this.blocking = false;
}
/**
Creates a duplicate of this animation. The list of frames
are shared between the two Animations, but each Animation
can be animated independently.
*/
public Object clone() {
return new Tile(gc, image, blocking);
}
/**
* Used to add an overlay to the existing tile
* #param image2 The image to overlay
*/
public void addImage(Image image2){
BufferedImage base = (BufferedImage)image;
BufferedImage overlay = (BufferedImage)image2;
// create the new image, canvas size is the max. of both image sizes
int w = Math.max(base.getWidth(), overlay.getWidth());
int h = Math.max(base.getHeight(), overlay.getHeight());
//BufferedImage combined = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
BufferedImage combined = gc.createCompatibleImage(w, h, Transparency.TRANSLUCENT);
// paint both images, preserving the alpha channels
Graphics g = combined.getGraphics();
g.drawImage(image, 0, 0, null);
g.drawImage(overlay, 0, 0, null);
this.image = (Image)combined;
}
public boolean isBlocking(){
return this.blocking;
}
public void setBlocking(boolean blocking){
this.blocking = blocking;
}
public Image getImage(){
return this.image;
}
}
I would use a pixel rendering engine (google it ;D)
Basically what you do, it have a giant array of intergers corresponding to the image you are drawing.
Basically you have each tile have an array of intergers representing its pixels.
When you render that tile, you "copy" (it's slightly more complicated than that) the array of tile to the big array :)
Then once you are done rendering everything to the master array, you draw that on the screen.
This way, you are only dealing with integers and not whole pictures everytime you draw something. This makes it a lot faster.
I learned this using MrDeathJockey's (youtube) tutorials and combining them with DesignsbyZephyr's (also youtube). Although I do not recommend using his technique (he only uses 4 colors and 8 bit graphics, as with deathJockey's tutorials you can customize the size of the images and even have multiple sprite sheets with different resolutions (useful for fonts)
I did however use some of the offset stuff (to make the screen move instead of the player) and the InputHandler by Zephyr :)
Hope this helps!
-Camodude009
Create transparent images (not translucent) since translucent images require higher ram and are stored in the system memory instead of the standard java heap. Creating translucent images require several native calls to access the native system memory. Use BufferedImages instead of Image. You can cast BufferedImage to Image at any time.