Problems with BufferedImage SubImages - java

I am currently trying to get my java program to load separate sprites from a sprite sheet. When I try to get The yellow square in this sprite sheet
.
However, what I get is this
.
Prior to this I was able to load and render an entire PNG file without problems, and I am unsure the reason for it not working. I put snippets of code that I use to load images, render images, and getting the subimage. I'm still somewhat new to java, so I'm not sure if there is a better solution to this
//The code I use to load a PNG as a BufferedImage
public BufferedImage loadImage(String path) {
try {
BufferedImage loadedImage = ImageIO.read(new FileInputStream(path));
BufferedImage formattedImage = new BufferedImage(loadedImage.getWidth(), loadedImage.getHeight(), BufferedImage.TYPE_INT_RGB);
formattedImage.getGraphics().drawImage(loadedImage, 0, 0, null);
return formattedImage;
} catch (IOException e) {
e.printStackTrace();
return null;
}
//The code I use to render an image onto the screen
public void renderImage(BufferedImage image, int xpos, int ypos) {
int[] imgpixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
if (!(x + xpos > getScreenWidth() - 1) && !(y + ypos > getScreenHeight() - 1) && !(x + xpos < 0) && !(y + ypos < 0)) {
pixels[(x + xpos) + (y + ypos) * getScreenWidth()] = imgpixels[x + y * image.getWidth()];
}
}
}
}
//the code I used to get the subimage
public BufferedImage getImage(int index) {
int i = index % (row * col);
int x = tileX * (i % col);
int y = tileY * (i % row);
return spriteSheet.getSubimage(x, y, tileX, tileY);
//still does the same thing if I were to put in spriteSheet.getSubimage(0, 0, 16, 16)
}

Related

org.eclipse.swt.SWTException: Unsupported color depth

I have created a sample SWT application. I am uploading few images into the application. I have to resize all the images which are above 16x16 (Width*Height) resolution and save those in separate location.
For this reason I am scaling the image and saving the scaled image to my destination location. Below is the piece of code which I am using to do that.
Using getImageData() to get the image data and to save I am using ImageLoader save() method.
final Image mySampleImage = ImageResizer.scaleImage(img, 16, 16);
final ImageLoader imageLoader = new ImageLoader();
imageLoader.data = new ImageData[] { mySampleImage.getImageData() };
final String fileExtension = inputImagePath.substring(inputImagePath.lastIndexOf(".") + 1);
if ("GIF".equalsIgnoreCase(fileExtension)) {
imageLoader.save(outputImagePath, SWT.IMAGE_GIF);
} else if ("PNG".equalsIgnoreCase(fileExtension)) {
imageLoader.save(outputImagePath, SWT.IMAGE_PNG);
}
ImageLoader imageLoader.save(outputImagePath, SWT.IMAGE_GIF); is throwing the below exeception when I am trying to save few specific images (GIF or PNG format).
org.eclipse.swt.SWTException: Unsupported color depth
at org.eclipse.swt.SWT.error(SWT.java:4533)
at org.eclipse.swt.SWT.error(SWT.java:4448)
at org.eclipse.swt.SWT.error(SWT.java:4419)
at org.eclipse.swt.internal.image.GIFFileFormat.unloadIntoByteStream(GIFFileFormat.java:427)
at org.eclipse.swt.internal.image.FileFormat.unloadIntoStream(FileFormat.java:124)
at org.eclipse.swt.internal.image.FileFormat.save(FileFormat.java:112)
at org.eclipse.swt.graphics.ImageLoader.save(ImageLoader.java:218)
at org.eclipse.swt.graphics.ImageLoader.save(ImageLoader.java:259)
at mainpackage.ImageResizer.resize(ImageResizer.java:55)
at mainpackage.ImageResizer.main(ImageResizer.java:110)
Let me know If there is any other way to do the same (or) there is any way to resolve this issue.
Finally I got a solution by referring to this existing eclipse bug Unsupported color depth eclipse bug.
In the below code i have created a PaletteData with RGB values and updated my Image Data.
My updateImagedata() method will take the scaled image and will return the proper updated imageData if the image depth is 32 or more.
private static ImageData updateImagedata(Image image) {
ImageData data = image.getImageData();
if (!data.palette.isDirect && data.depth <= 8)
return data;
// compute a histogram of color frequencies
HashMap<RGB, ColorCounter> freq = new HashMap<>();
int width = data.width;
int[] pixels = new int[width];
int[] maskPixels = new int[width];
for (int y = 0, height = data.height; y < height; ++y) {
data.getPixels(0, y, width, pixels, 0);
for (int x = 0; x < width; ++x) {
RGB rgb = data.palette.getRGB(pixels[x]);
ColorCounter counter = (ColorCounter) freq.get(rgb);
if (counter == null) {
counter = new ColorCounter();
counter.rgb = rgb;
freq.put(rgb, counter);
}
counter.count++;
}
}
// sort colors by most frequently used
ColorCounter[] counters = new ColorCounter[freq.size()];
freq.values().toArray(counters);
Arrays.sort(counters);
// pick the most frequently used 256 (or fewer), and make a palette
ImageData mask = null;
if (data.transparentPixel != -1 || data.maskData != null) {
mask = data.getTransparencyMask();
}
int n = Math.min(256, freq.size());
RGB[] rgbs = new RGB[n + (mask != null ? 1 : 0)];
for (int i = 0; i < n; ++i)
rgbs[i] = counters[i].rgb;
if (mask != null) {
rgbs[rgbs.length - 1] = data.transparentPixel != -1 ? data.palette.getRGB(data.transparentPixel)
: new RGB(255, 255, 255);
}
PaletteData palette = new PaletteData(rgbs);
ImageData newData = new ImageData(width, data.height, 8, palette);
if (mask != null)
newData.transparentPixel = rgbs.length - 1;
for (int y = 0, height = data.height; y < height; ++y) {
data.getPixels(0, y, width, pixels, 0);
if (mask != null)
mask.getPixels(0, y, width, maskPixels, 0);
for (int x = 0; x < width; ++x) {
if (mask != null && maskPixels[x] == 0) {
pixels[x] = rgbs.length - 1;
} else {
RGB rgb = data.palette.getRGB(pixels[x]);
pixels[x] = closest(rgbs, n, rgb);
}
}
newData.setPixels(0, y, width, pixels, 0);
}
return newData;
}
To find minimum index:
static int closest(RGB[] rgbs, int n, RGB rgb) {
int minDist = 256*256*3;
int minIndex = 0;
for (int i = 0; i < n; ++i) {
RGB rgb2 = rgbs[i];
int da = rgb2.red - rgb.red;
int dg = rgb2.green - rgb.green;
int db = rgb2.blue - rgb.blue;
int dist = da*da + dg*dg + db*db;
if (dist < minDist) {
minDist = dist;
minIndex = i;
}
}
return minIndex;
}
ColourCounter Class:
class ColorCounter implements Comparable<ColorCounter> {
RGB rgb;
int count;
public int compareTo(ColorCounter o) {
return o.count - count;
}
}

Converting monochrome image to minimum number of 2d shapes

Basically, what I need to do is take a 2d array of bitflags and produce a list of 2d rectangles to fill the entire area with the minimum number of total shapes required to perfectly fill the space. I am doing this to convert a 2d top-down monochrome of a map into 2d rectangle shapes which perfectly represent the passed in image which will be used to generate a platform in a 3d world. I need to minimize the total number of shapes used, because each shape will represent a separate object, and flooding it with 1 unit sized squares for each pixel would be highly inefficient for that engine.
So far I have read in the image, processed it, and filled a two dimensional array of booleans which tells me if the pixel should be filled or unfilled, but I am unsure of the most efficient approach of continuing.
Here is what I have so far, as reference, if you aren't following:
public static void main(String[] args) {
File file = new File(args[0]);
BufferedImage bi = null;
try {
bi = ImageIO.read(file);
} catch (IOException ex) {
Logger.global.log(Level.SEVERE, null, ex);
}
if (bi != null) {
int[] rgb = bi.getRGB(0, 0, bi.getWidth(), bi.getHeight(), new int[bi.getWidth() * bi.getHeight()], 0, bi.getWidth());
Origin origin = new Origin(bi.getWidth() / 2, bi.getHeight() / 2);
boolean[][] flags = new boolean[bi.getWidth()][bi.getHeight()];
for (int y = 0; y < bi.getHeight(); y++) {
for (int x = 0; x < bi.getWidth(); x++) {
int index = y * bi.getWidth() + x;
int color = rgb[index];
int type = color == Color.WHITE.getRGB() ? 1 : (color == Color.RED.getRGB() ? 2 : 0);
if (type == 2) {
origin = new Origin(x, y);
}
flags[x][y] = type != 1;
}
}
List<Rectangle> list = new ArrayList();
//Fill list with rectangles
}
}
White represents no land. Black or Red represents land. The check for the red pixel marks the origin position of map, which was just for convenience and the rectangles will be offset by the origin position if it is found.
Edit: The processing script does not need to be fast, the produced list of rectangles will be dumped and that will be what will be imported and used later, so the processing of the image does not need to be particularly optimized, it doesn't make a difference.
I also just realized that expecting a 'perfect' solution is expecting too much, since this would qualify as a 'knapsack problem' of the multidimensionally constrained variety, if I am expecting exactly the fewest number of rectangles, so simply an algorithm that produces a minimal number of rectangles will suffice.
Here is a reference image for completion:
Edit 2: It doesn't look like this is such an easy thing to answer given no feedback yet, but I have started making progress, but I am sure I am missing something that would vastly reduce the number of rectangles. Here is the updated progress:
static int mapWidth;
static int mapHeight;
public static void main(String[] args) {
File file = new File(args[0]);
BufferedImage bi = null;
System.out.println("Reading image...");
try {
bi = ImageIO.read(file);
} catch (IOException ex) {
Logger.global.log(Level.SEVERE, null, ex);
}
if (bi != null) {
System.out.println("Complete!");
System.out.println("Interpreting image...");
mapWidth = bi.getWidth();
mapHeight = bi.getHeight();;
int[] rgb = bi.getRGB(0, 0, mapWidth, mapHeight, new int[mapWidth * mapHeight], 0, mapWidth);
Origin origin = new Origin(mapWidth / 2, mapHeight / 2);
boolean[][] flags = new boolean[mapWidth][mapHeight];
for (int y = 0; y < mapHeight; y++) {
for (int x = 0; x < mapWidth; x++) {
int index = y * mapWidth + x;
int color = rgb[index];
int type = color == Color.WHITE.getRGB() ? 1 : (color == Color.RED.getRGB() ? 2 : 0);
if (type == 2) {
origin = new Origin(x, y);
}
flags[x][y] = type != 1;
}
}
System.out.println("Complete!");
System.out.println("Processing...");
//Get Rectangles to fill space...
List<Rectangle> rectangles = getRectangles(flags, origin);
System.out.println("Complete!");
float rectangleCount = rectangles.size();
float totalCount = mapHeight * mapWidth;
System.out.println("Total units: " + (int)totalCount);
System.out.println("Total rectangles: " + (int)rectangleCount);
System.out.println("Rectangle reduction factor: " + ((1 - rectangleCount / totalCount) * 100.0) + "%");
System.out.println("Dumping data...");
try {
file = new File(file.getParentFile(), file.getName() + "_Rectangle_Data.txt");
if(file.exists()){
file.delete();
}
file.createNewFile();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
for(Rectangle rect: rectangles){
bw.write(rect.x + "," + rect.y + "," + rect.width + ","+ rect.height + "\n");
}
bw.flush();
bw.close();
} catch (Exception ex) {
Logger.global.log(Level.SEVERE, null, ex);
}
System.out.println("Complete!");
}else{
System.out.println("Error!");
}
}
public static void clearRange(boolean[][] flags, int xOff, int yOff, int width, int height) {
for (int y = yOff; y < yOff + height; y++) {
for (int x = xOff; x < xOff + width; x++) {
flags[x][y] = false;
}
}
}
public static boolean checkIfFilled(boolean[][] flags, int xOff, int yOff, int width, int height) {
for (int y = yOff; y < yOff + height; y++) {
for (int x = xOff; x < xOff + width; x++) {
if (!flags[x][y]) {
return false;
}
}
}
return true;
}
public static List<Rectangle> getRectangles(boolean[][] flags, Origin origin) {
List<Rectangle> rectangles = new ArrayList();
for (int y = 0; y < mapHeight; y++) {
for (int x = 0; x < mapWidth; x++) {
if (flags[x][y]) {
int maxWidth = 1;
int maxHeight = 1;
Loop:
//The search size limited to 400x400 so it will complete some time this century.
for (int w = Math.min(400, mapWidth - x); w > 1; w--) {
for (int h = Math.min(400, mapHeight - y); h > 1; h--) {
if (w * h > maxWidth * maxHeight) {
if (checkIfFilled(flags, x, y, w, h)) {
maxWidth = w;
maxHeight = h;
break Loop;
}
}
}
}
//Search also in the opposite direction
Loop:
for (int h = Math.min(400, mapHeight - y); h > 1; h--) {
for (int w = Math.min(400, mapWidth - x); w > 1; w--) {
if (w * h > maxWidth * maxHeight) {
if (checkIfFilled(flags, x, y, w, h)) {
maxWidth = w;
maxHeight = h;
break Loop;
}
}
}
}
rectangles.add(new Rectangle(x - origin.x, y - origin.y, maxWidth, maxHeight));
clearRange(flags, x, y, maxWidth, maxHeight);
}
}
}
return rectangles;
}
My current code's search for larger rectangles is limited to 400x400 to speed up testing, and outputs 17,979 rectangles, which is a 99.9058% total reduction of rectangles if I treated each pixel as a 1x1 square(19,095,720 pixels). So far so good.

Input an image from some saved location on computer

I have a draw function
public void drawBoard(Graphics g) {
int height = this.getHeight();
int width = this.getWidth();
int dx = width / 7;
int dy = height / 6;
for (int x = 0, row = 0; x <= width && row < gameboard.length; row++, x += dx) {
for (int col = 0, y = 0; y <= height&& col < gameboard[0].length; y += dy, col++) {
if (gameboard[row][col] == 0) {
g.setColor(Color.GRAY);
g.fillOval(y, x, dy, dx);
} else if (gameboard[row][col] == 1) {
g.setColor(Color.RED);
g.fillOval(y, x, dy, dx);
} else if(gameboard[row][col] == 1){
g.setColor(Color.BLACK);
g.fillOval(y, x, dy, dx);
} else if(gameboard[row][col]==3){
}else if(gameboard[row][col]==4){
}else if(gameboard[row][col]==5){
}else if(gameboard[row][col]==6){
}else if(gameboard[row][col]==7){
}else if(gameboard[row][col]==8){
}else if(gameboard[row][col]==9){
}
}
}
}
However for when gameboard[row][col]=3,4,...9 I want it to change that slot into a picture downloaded from the web. How do I do that?
I would prefer to do it without a URL definition and simply a get Document like thing in html where I have the photos saved in a file
First, you need to get your image as a BufferedImage. I suggest using the ImageIO class:
String imageFileName = "myTestFile.jpg"
BufferedImage img = ImageIO.read(((new File(imageFileName)).toURI()).toURL());
Next, you want to draw your image using the Graphics.drawImage() API. Your comment indicates that you think you'll need to scale the image, so use the appropriate drawImage method to do that. Using your Graphics object g from your code above, this might look like::
int oldWidth = img.getWidth();
int oldHeight = img.getHeight();
int newWidth = 10; //You decide this...
int newHeight = 10; //You decide this too...
g.drawImage(img, 0, 0, oldWidth, newWidth, 0, 0, newWidth, newHeight, null);
Oracle themselves have an applet which demonstrates the code for this approach, along with a number of other common operations you might want to do on images in their ImageDrawingApplet.java code

Rotating a BufferedImage and Saving it into a pixel array

I am trying to properly rotate a sword in my 2D game. I have a sword image file, and I wish to rotate the image at the player's location. I tried using Graphics2D and AffineTransform, but the problem is that the player moves on a different coordinate plane, the Screen class, and the Graphics uses the literal location of the pixels on the JFrame. So, I realized that I need to render the sword by rotating the image itself, and then saving it into a pixel array for my screen class to render. However, I don't know how to do this. Here is the code for my screen rendering method:
public void render(double d, double yOffset2, BufferedImage image, int colour,
int mirrorDir, double scale, SpriteSheet sheet) {
d -= xOffset;
yOffset2 -= yOffset;
boolean mirrorX = (mirrorDir & BIT_MIRROR_X) > 0;
boolean mirrorY = (mirrorDir & BIT_MIRROR_Y) > 0;
double scaleMap = scale - 1;
for (int y = 0; y < image.getHeight(); y++) {
int ySheet = y;
if (mirrorY)
ySheet = image.getHeight() - 1 - y;
int yPixel = (int) (y + yOffset2 + (y * scaleMap) - ((scaleMap * 8) / 2));
for (int x = 0; x < image.getWidth(); x++) {
int xPixel = (int) (x + d + (x * scaleMap) - ((scaleMap * 8) / 2));
int xSheet = x;
if (mirrorX)
xSheet = image.getWidth() - 1 - x;
int col = (colour >> (sheet.pixels[xSheet + ySheet
* sheet.width])) & 255;
if (col < 255) {
for (int yScale = 0; yScale < scale; yScale++) {
if (yPixel + yScale < 0 || yPixel + yScale >= height)
continue;
for (int xScale = 0; xScale < scale; xScale++) {
if (x + d < 0 || x + d >= width)
continue;
pixels[(xPixel + xScale) + (yPixel + yScale)
* width] = col;
}
}
}
}
}
}
Here is one of my poor attempts to call the render method from the Sword Class:
public void render(Screen screen) {
AffineTransform at = new AffineTransform();
at.rotate(1, image.getWidth() / 2, image.getHeight() / 2);
AffineTransformOp op = new AffineTransformOp(at,
AffineTransformOp.TYPE_BILINEAR);
image = op.filter(image, null);
screen.render(this.x, this.y, image, SwordColor, 1, 1.5, sheet);
hitBox.setLocation((int) this.x, (int) this.y);
for (Entity entity : level.getEntities()) {
if (entity instanceof Mob) {
if (hitBox.intersects(((Mob) entity).hitBox)) {
// ((Mob) entity).health--;
}
}
}
}
Thank you for any help you can provide, and please feel free to tell me if theres a better way to do this.
You can rotate() the image around an anchor point, also seen here in a Graphics2D context. The method concatenates translate(), rotate() and translate() operations, also seen here as explicit transformations.
Addendum: It rotates the image, but how do I save the pixels of the image as an array?
Once you filter() the image, use one of the ImageIO.write() methods to save the resulting RenderedImage, for example.

Flood Fill Algorithm Resulting in Black Image

I'm constructing a flood fill algorithm that will hopefully, eventually, find a face in the center of a photograph based on the color at the exact center, and similar colors surrounding that. At the moment, however, my algorithm should take in any color within the bounds of the int array and transfer it over to a holder array, essentially making a copy of the original image. But this isn't working, and is resulting in a black image when I run it. Can anyone see the problem I'm missing?
public class TemplateMaker {
public static void main(String[] args) throws IOException {
importPhoto();
}
public static void importPhoto() throws IOException {
File imgPath = new File("/Pictures/BaseImage.JPG");
BufferedImage bufferedImage = ImageIO.read(imgPath);
establishArray(bufferedImage);
}
public static void establishArray(BufferedImage bufferedImage) throws IOException {
//byte[] pixels = hugeImage.getData();
int width = bufferedImage.getWidth();
System.out.println(width);
int height = bufferedImage.getHeight();
System.out.println(height);
int[][] result = new int[height][width];
for (int i = 0; i < height; i++)
for (int j = 0; j < width; j++) {
result[i][j] = bufferedImage.getRGB(j, i);
}
findFace(result);
}
public static void findFace(int[][] image) throws IOException {
int height = image.length;
int width = image[0].length;
Color centerStart = new Color(image[height / 2][width / 2], true);
System.out.println(centerStart.getRGB());
System.out.println(Color.blue.getRGB());
int[][] filled = new int[height][width];
floodFill(height / 2, width / 2, centerStart, image, filled, height, width);
//construct the filled array as image.
BufferedImage bufferImage2 = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < height; x++) {
for (int y = 0; y < width; y++) {
bufferImage2.setRGB(y, x, filled[x][y]);
}
}
//save filled array as image file
File outputfile = new File("/Pictures/saved.jpg");
ImageIO.write(bufferImage2, "jpg", outputfile);
}
public static int[][] floodFill(int x, int y, Color targetColor, int[][] image, int[][] filled, int height, int width) {
//execute something similar once algorithm works.
// if (image[x][y] < targetColor.getRGB()/2 || image[x][y] > targetColor.getRGB()*2) return filled;
if (image[x][y] == Color.blue.getRGB()) {
return filled;
}
if (image.length < 0 || image[0].length < 0 || image.length >= height || image[0].length >= width) {
return filled;
}
filled[x][y] = image[x][y];
image[x][y] = Color.blue.getRGB();
floodFill(x - 1, y, targetColor, image, filled, height, width);
floodFill(x + 1, y, targetColor, image, filled, height, width);
floodFill(x, y - 1, targetColor, image, filled, height, width);
floodFill(x, y + 1, targetColor, image, filled, height, width);
return filled;
}
}
You create int[][] called filled and then call floodFill(...) which returns without doing anything to the array. image.length is always equal to height and image[0].length is always equal to width, so it always returns from the second if statement.
You then build a BufferedImage from that blank array and write it to a file. All of the values in the array are initialized to 0, which gives you black.
Changing the for loop in findFace(..) to the below will save out the original image from your holder array.
for (int x = 0; x < height; x++) {
for (int y = 0; y < width; y++) {
bufferImage2.setRGB(y, x, image[x][y]);
}
}
But I'm not sure if this is what you're asking or not.
Edit: Try this out and see if it sends you in the right direction:
public static int[][] floodFill(int x, int y, Color targetColor, int[][] image, int[][] filled, int height, int width) {
//execute something similar once algorithm works.
// if (image[x][y] < targetColor.getRGB()/2 || image[x][y] > targetColor.getRGB()*2) return filled;
if (image[x][y] == Color.blue.getRGB()) {
System.out.println("returned if 1");
return filled;
}
/*if (image.length < 0 || image[0].length < 0 || image.length >= height || image[0].length >= width) {
return filled;
}*/
filled[x][y] = image[x][y];
image[x][y] = Color.blue.getRGB();
if (x - 1 <= 0 && y < width) {
floodFill(x - 1, y, targetColor, image, filled, height, width);
}
if(x + 1 < height && y >= 0 && y < width) {
floodFill(x + 1, y, targetColor, image, filled, height, width);
}
if(x >= 0 && x < height && y - 1 <= 0) {
floodFill(x, y - 1, targetColor, image, filled, height, width);
}
if(x >= 0 && x < height && y + 1 < width) {
floodFill(x, y + 1, targetColor, image, filled, height, width);
}
return filled;
}

Categories

Resources