Graphics fill rect overflow into new rects? - java

I am trying to make a simple program to "pixelate" images. (make them look like 8 bit drawings). Basically how it works is that it loops through images in a given directory, asks for a percent of the width for a new "pixel" size, and then "pixelates" the image. It does this by looping through each new "pixel" and finding an average color for that square region and saving it to a 2D array of colors. Then it loops through the 2D array and draws rectangles on a new image that are the correct "pixel" size.
I'm having an issue with transparency in some of the pixels, and this is important because I want to be able to work with PNG files with transparent backgrounds. Here is what the before and after pictures look like:
Before and After. Obviously, the pixels that come after the centered image should still be transparent. I observed every "pixel"'s color and it shows that some are transparent even though they are being filled in black towards the bottom right of the picture. Here is my whole class:
package pixelator;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
import javax.imageio.ImageIO;
public class Pixelator {
public static void main(String[] args) throws IOException{
Scanner scan = new Scanner(System.in);
File folder = new File(args[0]);
File[] files = folder.listFiles();
if(files.length == 0) {
System.out.println("No files in given directory.");
}
for(int i = 0; i < files.length; i ++) {
if(filterFile(files[i])) {
BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
img = ImageIO.read(files[i]);
System.out.println("File: " + files[i].getName());
System.out.print("Enter pixelated size percentage(out of 100): ");
double percent = scan.nextDouble();
img = pixelate(img, percent);
File toWrite = new File(args[1] + "\\pixelated_" + files[i].getName());
ImageIO.write(img, files[i].getName().substring(files[i].getName().indexOf(".") + 1, files[i].getName().length()), toWrite);
System.out.println("New image saved");
}
}
}
/**
* returns a pixelated version of the original image with the pixels being the given percent
* if the image's size. Compensates for size of image.
* #param img
* #param percent
* #return BufferedImage
*/
public static BufferedImage pixelate(BufferedImage img, double percent) {
//find the number of pixels in the new "pixel"
int newPixelSize = (int)((percent/100.0) * img.getWidth());
int width = newPixelSize * (img.getWidth() / newPixelSize);
int height = newPixelSize * (img.getHeight() / newPixelSize);
System.out.println("Old Width: " + img.getWidth() + "\nOld Height: " + img.getHeight());
System.out.println("New pixel size: " + newPixelSize + "\nNew Width: " + width + "\nnew Height: " + height);
BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Color[][] pixelArray = new Color[height / newPixelSize][width / newPixelSize];
for(int i = 0; i < pixelArray.length; i ++) {
for(int j = 0; j < pixelArray[0].length; j ++) {
pixelArray[i][j] = findColorAtPixelCoordinates(i, j, newPixelSize, img);
}
}
for(int i = 0; i < pixelArray.length; i ++) {
for(int j = 0; j < pixelArray[0].length; j ++) {
newImage = setNewImagePixel(i, j, newPixelSize, newImage, pixelArray[i][j]);
}
}
return newImage;
}
/**
* gets the average color over a certain rectangle on the original image
* #param y
* #param x
* #param pixelSize
* #param img
* #return
*/
public static Color findColorAtPixelCoordinates(int y, int x, int pixelSize, BufferedImage img) {
int[] averageARGB = {0, 0, 0, 0};
x = x * pixelSize;
y = y * pixelSize;
//loop through a certain "pixel" contained in the img, adding all of the values to the array
for(int i = y; i < y + pixelSize; i ++) {
for(int j = x; j < x + pixelSize; j++) {
Color colorAtPixel = new Color(img.getRGB(j, i), true);
averageARGB[0] += colorAtPixel.getRed();
averageARGB[1] += colorAtPixel.getGreen();
averageARGB[2] += colorAtPixel.getBlue();
averageARGB[3] += colorAtPixel.getAlpha();
}
}
//calculate the averages
averageARGB[0] = averageARGB[0] / (pixelSize * pixelSize);
averageARGB[1] = averageARGB[1] / (pixelSize * pixelSize);
averageARGB[2] = averageARGB[2] / (pixelSize * pixelSize);
averageARGB[3] = averageARGB[3] / (pixelSize * pixelSize);
return new Color(averageARGB[0], averageARGB[1], averageARGB[2], averageARGB[3]);
}
/**
* sets a new "pixel" rectangle for the new image. Also prints out each new "pixels: coordinates and color
* for testing
* #param y
* #param x
* #param pixelSize
* #param newImage
* #param color
* #return
*/
public static BufferedImage setNewImagePixel(int y, int x, int pixelSize, BufferedImage newImage, Color color) {
System.out.println("Row: "+ y + ", Column: " + x + ", Color: [" + color.getRed() + ", " + color.getGreen() + ", " + color.getBlue() + ", " + color.getAlpha()+"]");
x = x * pixelSize;
y = y * pixelSize;
// get the graphics then fill the rect with the right color
Graphics2D g = (Graphics2D) newImage.getGraphics();
g.setColor(color);
g.fillRect(x, y, x + pixelSize, y + pixelSize);
return newImage;
}
/**
* return true if the image is a png or jpg file
* #param fileName
* #return boolean
*/
public static boolean filterFile(File fileName) {
//System.out.println(fileName.substring(fileName.indexOf("."), fileName.length()));
if(!fileName.isFile()) {
return false;
}
return fileName.getName().substring(fileName.getName().indexOf("."), fileName.getName().length()).equals(".jpg") ||
fileName.getName().substring(fileName.getName().indexOf("."), fileName.getName().length()).equals(".jpeg") ||
fileName.getName().substring(fileName.getName().indexOf("."), fileName.getName().length()).equals(".png");
}
}
Thanks for any help you can provide!

First thing you are doing wrong is in function setNewImagePixel:
g.fillRect(x, y, x+pixelSize, y+pixelSize);
Instead of providing width and height, you are providing bottom right corner, So correct it using
g.fillRect(x, y, pixelSize, pixelSize);
Another thing is you should not average the alpha component in function findColorAtPixelCoordinates. You should write it as
Color color = new Color(img.getRGB(x, y), true);
averageARGB[3] = color.getAlpha();

Related

Calculate Skintone using JCuda is not giving the right percentage

Im calculting skintone of an image in java.
convert the pixel of Image in yCbCR.
check if image pixel is in specific range, then its a skin color.
calculate percentage by dividing it by total pixel.
Its working fine in CPU code, but when i convert it to GPU code, The pixel percentage is not coming right.
The confusing part for me was send the pixel data to GPU and get its r, g, b value in GPU.
So i follow JCuda Pixel Invert Example example to send pixel data. The difference is the example send pixel data in int[] array and I'm sending it in byte[] array.
Here the code.
import static jcuda.driver.JCudaDriver.cuCtxCreate;
import static jcuda.driver.JCudaDriver.cuCtxSynchronize;
import static jcuda.driver.JCudaDriver.cuDeviceGet;
import static jcuda.driver.JCudaDriver.cuInit;
import static jcuda.driver.JCudaDriver.cuLaunchKernel;
import static jcuda.driver.JCudaDriver.cuMemAlloc;
import static jcuda.driver.JCudaDriver.cuMemFree;
import static jcuda.driver.JCudaDriver.cuMemcpyDtoH;
import static jcuda.driver.JCudaDriver.cuMemcpyHtoD;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import ij.IJ;
import jcuda.Pointer;
import jcuda.Sizeof;
import jcuda.driver.CUcontext;
import jcuda.driver.CUdevice;
import jcuda.driver.CUdeviceptr;
import jcuda.driver.CUfunction;
import jcuda.driver.JCudaDriver;
import jcuda.nvrtc.JNvrtc;
public class SkinTone {
public static void CalculateSKintoneGPU(File file) throws IOException {
BufferedImage bufferedImage = ImageIO.read(file);
if (bufferedImage == null || bufferedImage.getData() == null)
return;
Raster raster = bufferedImage.getData();
DataBuffer dataBuffer = raster.getDataBuffer();
DataBufferByte dataBufferInt = (DataBufferByte)dataBuffer;
byte[] pixels = dataBufferInt.getData();
int totalPixels = raster.getHeight() * raster.getWidth();
CUfunction kernelFunction = initlize();
int output[] = execute(kernelFunction, pixels, raster.getWidth(), raster.getHeight());
// Flushing memory
raster = null;
bufferedImage.flush();
bufferedImage = null;
long skintoneThreshold = Math.round(output[0] / (double) totalPixels * 100.0);
System.err.println("Skintone Using GPU: " + output[0]);
System.err.println("Total Pixel Of GPU: " + totalPixels);
System.err.println("SKinTone Percentage Using GPU: " + skintoneThreshold + "%");
}
static int[] execute(CUfunction kernelFunction, byte[] pixels, int w, int h) {
// Allocate memory on the device, and copy the host data to the device
int size = w * h * Sizeof.BYTE;
CUdeviceptr pointer = new CUdeviceptr();
cuMemAlloc(pointer, size);
cuMemcpyHtoD(pointer, Pointer.to(pixels), size);
int numElements = 9;
int s = 0;
// Allocate device output memory
CUdeviceptr deviceOutput = new CUdeviceptr();
cuMemAlloc(deviceOutput, numElements * Sizeof.INT);
// Set up the kernel parameters: A pointer to an array
// of pointers which point to the actual values.
Pointer kernelParameters = Pointer.to(Pointer.to(pointer), Pointer.to(new int[] { w }),
Pointer.to(new int[] { h }), Pointer.to(deviceOutput));
// Call the kernel function
int blockSize = 16;
int gridSize = (Math.max(w, h) + blockSize - 1) / blockSize;
cuLaunchKernel(kernelFunction, gridSize, gridSize, 1, // Grid dimension
blockSize, blockSize, 1, // Block dimension
0, null, // Shared memory size and stream
kernelParameters, null // Kernel- and extra parameters
);
cuCtxSynchronize();
// Allocate host output memory and copy the device output
// to the host.
int hostOutput[] = new int[numElements];
cuMemcpyDtoH(Pointer.to(hostOutput), deviceOutput, numElements * Sizeof.INT);
// Clean up.
cuMemFree(deviceOutput);
cuMemFree(pointer);
return hostOutput;
}
public static CUfunction initlize() {
// Enable exceptions and omit all subsequent error checks
JCudaDriver.setExceptionsEnabled(true);
JNvrtc.setExceptionsEnabled(true);
// Initialize the driver and create a context for the first device.
cuInit(0);
CUdevice device = new CUdevice();
cuDeviceGet(device, 0);
CUcontext context = new CUcontext();
cuCtxCreate(context, 0, device);
// Obtain the CUDA source code from the CUDA file
String cuFileName = "Skintone.cu";
String sourceCode = CudaUtils.readResourceAsString(cuFileName);
if (sourceCode == null) {
IJ.showMessage("Error", "Could not read the kernel source code");
}
// Create the kernel function
return CudaUtils.createFunction(sourceCode, "skintone");
}
public static void CalculateSKintoneCPU(File file) throws IOException {
BufferedImage bufferedImage = ImageIO.read(file);
if (bufferedImage == null || bufferedImage.getData() == null)
return;
Raster raster = bufferedImage.getData();
float[] rgb = new float[4];
int totalPixels = raster.getHeight() * raster.getWidth();
int skinTonePixels = 0;
for (int x = 0; x < raster.getWidth(); x++) {
for (int y = 0; y < raster.getHeight(); y++) {
raster.getPixel(x, y, rgb);
if (skintone(rgb)) {
skinTonePixels++;
}
}
}
// Flushing memory
raster = null;
rgb = null;
bufferedImage.flush();
bufferedImage = null;
long skintoneThreshold = Math.round(skinTonePixels / (double) totalPixels * 100.0);
System.err.println("Skintone Using CPU: " + skinTonePixels);
System.err.println("Total Pixel Of CPU: " + totalPixels);
System.err.println("SKinTone Percentage Using CPU: " + skintoneThreshold + "%");
}
private static boolean skintone(float[] rgb) {
float yCbCr[] = (float[]) convertRGBtoYUV(rgb);
if ((yCbCr[1] >= 80 && yCbCr[1] <= 120) && (yCbCr[2] >= 133 && yCbCr[2] <= 173)) {
return true;
}
return false;
}
private static float[] convertRGBtoYUV(float[] rgb) {
final float[] yCbCr = new float[3];
float r = rgb[0];
float g = rgb[1];
float b = rgb[2];
yCbCr[0] = 16 + (0.299f * r) + (0.587f * g) + (0.144f * b);
yCbCr[1] = 128 + (-0.169f * r) - (0.331f * g) + (0.5f * b);
yCbCr[2] = 128 + (0.5f * r) - (0.419f * g) - (0.081f * b);
return yCbCr;
}
public static void main(String[] args) throws IOException {
File file = new File("C:\\Users\\Aqeel\\git\\jcuda-imagej-example\\src\\test\\resources\\lena512color.png");
CalculateSKintoneCPU(file);
CalculateSKintoneGPU(file);
}
}
Kernal File
extern "C"
__global__ void skintone(uchar4* data, int w, int h, int* output)
{
int x = threadIdx.x+blockIdx.x*blockDim.x;
int y = threadIdx.y+blockIdx.y*blockDim.y;
if (x < w && y < h)
{
float r, g, b;
float cb, cr;
int index = y*w+x;
uchar4 pixel = data[index];
r = pixel.x;
g = pixel.y;
b = pixel.z;
cb = 128 + (-0.169f * r) - (0.331f * g) + (0.5f * b);
cr = 128 + (0.5f * r) - (0.419f * g) - (0.081f * b);
if((cb >= 80 && cb <= 120) && (cr >= 133 && cr <= 173)) {
atomicAdd(&output[0], 1);
}
}
}
Complete Example src, Machine Need Nvida Card, Cuda Toolkit V9 and Graphics Drivers
I solve the problem by hit and trial method. In the kernel i change the position of r with b, and the problem resolved, also instead of byte i have to send the code in int array in java.
extern "C"
__global__ void skintone(uchar4* data, int w, int h, int* output)
{
int x = threadIdx.x+blockIdx.x*blockDim.x;
int y = threadIdx.y+blockIdx.y*blockDim.y;
if (x < w && y < h)
{
float b, g, r;
float cb, cr;
int index = y*w+x;
uchar4 pixel = data[index];
b = (float)pixel.x;
g = (float)pixel.y;
r = (float)pixel.z;
cb = 128 + (-0.169f * r) - (0.331f * g) + (0.5f * b);
cr = 128 + (0.5f * r) - (0.419f * g) - (0.081f * b);
if((cb >= 80 && cb <= 120) && (cr >= 133 && cr <= 173)) {
atomicAdd(&output[0], 1);
}
}
}
Java Code Changes.
public static void calculateSkintoneGPU() throws IOException {
BufferedImage img = ImageIO.read(SkinTone.class.getClassLoader().getResource("images.jpg"));
if (img == null || img.getData() == null)
return;
int width = img.getWidth(null);
int height = img.getHeight(null);
int[] pixels = new int[width * height];
PixelGrabber pg = new PixelGrabber(img, 0, 0, width, height, pixels, 0, width);
try {
pg.grabPixels();
} catch (InterruptedException e){};
int totalPixels = width * height;
CUfunction kernelFunction = initlize();
int output[] = execute(kernelFunction, pixels, width, height);
// Flushing memory
img.flush();
img = null;
long skintoneThreshold = Math.round(output[0] / (double) totalPixels * 100.0);
System.err.println("Skintone Using GPU: " + output[0]);
System.err.println("Total Pixel Of GPU: " + totalPixels);
System.err.println("SKinTone Percentage Using GPU: " + skintoneThreshold + "%");
}

Recursive changing variables - Sierpinski carpet

I'm having some problems whit drawing a sierpinski carpet, and would apreciate any help.
I was able to define the stoping condition, draw the central rectangle, and recursively, draw the next level of the image, all while keeping count.
It just so happens that I can only draw on the top left side. I'd say I'm confusing variables, but I can't seem to figure it out. Would apreciate any help
This is the part of the code where i'm having problems.
int smallerWidth = newWidth / 3;
int smallerHeight = newHeight / 3;
int sX = 0;
int sY = 0;
if (currentDeep > 1) {
for (int i = 0; i < 3; i++) {
sX = width / 9 + (i * 3 * (width / 9));
sY = height / 9;
g.fillRect(sX, sY, smallerWidth, smallerHeight);
for (int j = 0; j < 3; j++) {
sY = height / 9 + (j * 3 * (height / 9));
g.fillRect(sX, sY, smallerWidth, smallerHeight);
}
}
return 1 + printSquares(g, sX, sY, newWidth, newHeight, currentDeep
- 1);
} else
return 1;
}
This is the full code
https://pastebin.com/WPJ5tG8w
In sum my question is. What should I change/create in order for my program to draw the remaining 7 squares?
The issue with your code is, that you are trying to perform actions for multiple layers of the recursion at once. Normally, in the recursion, you would only paint the Quadrado central, calculate the sizes and coordinates of the smaller rectangles, and call the method recursively. That way you ensure that the recursive calls do not influence the stuff that is already there.
private int printSquares(Graphics g, int xi, int yi, int width, int height, int currentDeep) {
//Quadrado central
int newWidth = width / 3;
int newHeight = height / 3;
int x = (width / 3) + xi;
int y = (height / 3) + yi;
g.fillRect(x, y, newWidth, newHeight);
int sX = 0;
int sY = 0;
if (currentDeep > 1) {
int sum = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
//This is the position of each of the small rectangles
sX = i * (width / 3) + xi;
sY = j * (height / 3) + yi;
// Call the method recursively in order to draw the smaller rectangles
sum += printSquares(g, sX, sY, newWidth, newHeight, currentDeep - 1);
}
}
return 1 + sum;
} else
return 1;
}
I hope, this resolves you issue.
I hope this is ok. This is amazing code. I took the liberty to complete this with the original code provided in the question and added the code that fixed it that Illedhar recommended as a added method. Here it is. Thank you for sharing this.
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
public class sierpinskicarpet {
public static Color BACKGROUNDCOLOR = new Color(0, 0, 150);
public static Color FOREGROUNDCOLOR = new Color(255, 180, 0);
// Padrao = 5, alterado
public static int DEEP = 10;
/**
* Build the frame and shows it
*/
public sierpinskicarpet(int deep) {
// the frame and title
JFrame frame = new JFrame();
frame.setTitle("...: Recursive Squares with deep " + deep + " :...");
// Dispose frame on click on close button
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
// set size and center frame on screen
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
// add print area occupying all the frame content area
frame.add(new PrintArea(deep));
// put frame visible
frame.setVisible(true);
}
/**
* Main method
*/
public static void main(String[] args)
{
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
// launch for 1 to DEEP squares frames
for (int i = DEEP; i >= 1; --i) {
// build a new object each time: objects will run
// independently
new sierpinskicarpet(i);
}
}
});
}
}
/**
* Our print area is, in fact, a label extended with the paint squares behavior
*/
class PrintArea extends JLabel {
private static final long serialVersionUID = 1L;
// local deep variable, will keep the registered deep for this the print
// area
int deep;
/**
* constructor
*/
public PrintArea(int deep) {
// call super, that is JLabel, constructor
super();
// set background color and set as well opaque to allow the background
// to be visible
setBackground(sierpinskicarpet.BACKGROUNDCOLOR);
setOpaque(true);
// save the deep
this.deep = deep;
}
/**
* paint method, called by JVM, when it is needed to update the PrintArea
*/
public void paint(Graphics g) {
// call paint from the JLABEL, draws the background of the PrintArea
super.paint(g);
// set drawing color
g.setColor(sierpinskicarpet.FOREGROUNDCOLOR);
// call the amazing print square method
int n = printSquares(g, 0, 0, getWidth(), getHeight(), this.deep);
// put to the world how much squares we printed
System.out.println("Deep = " + deep + ", squares painted: " + n);
}
/**
* Auxiliary method that will to the work. It must print a square with 1/3
* of the length of the frame and at the center and if not the bottom level
* ask to do the same for each of the other 8 square with 1/3 of length but
* called with the new deep
*/
private int printSquares(Graphics g, int xi, int yi, int width, int height, int currentDeep) {
//Quadrado central
int newWidth = width / 3;
int newHeight = height / 3;
int x = (width / 3) + xi;
int y = (height / 3) + yi;
g.fillRect(x, y, newWidth, newHeight);
int sX = 0;
int sY = 0;
if (currentDeep > 1) {
int sum = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
//This is the position of each of the small rectangles
sX = i * (width / 3) + xi;
sY = j * (height / 3) + yi;
// Call the method recursively in order to draw the smaller rectangles
sum += printSquares(g, sX, sY, newWidth, newHeight, currentDeep - 1);
}
}
return 1 + sum;
} else
return 1;
}
}
/*
Works Cited:
Recursive changing variables - sierpinski carpet. Stack Overflow. Retrieved May 4, 2022,
from https://stackoverflow.com/questions/49945862/recursive-changing-variables-sierpinski-carpet
*/

Can't draw BufferedImage into another BufferedImage with scale

Please advise.
I'm trying to draw input BufferedImage into larger output BufferedImage (with scaling). Please, take a look at the following code:
public class Main {
public void print(BufferedImage img, int width, int height) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
System.out.print(img.getRGB(x, y) + " ");
}
System.out.println("");
}
}
public static void main(String[] args) {
Main app = new Main();
// create input image
int inputWidth = 2;
int inputHeight = 2;
BufferedImage inputImg = new BufferedImage(inputWidth, inputHeight, BufferedImage.TYPE_INT_ARGB);
// fill input image
for (int y = 0; y < inputHeight; y++) {
for (int x = 0; x < inputWidth; x++) {
inputImg.setRGB(x, y, y * inputWidth * (1 << 16) + x);
}
}
// print
app.print(inputImg, inputWidth, inputHeight);
// create output image
int outputWidth = 4;
int outputHeight = 4;
BufferedImage outputImg = new BufferedImage(outputWidth, outputHeight, BufferedImage.TYPE_INT_ARGB);
// draw inputImg into outputImg
Graphics2D g = outputImg.createGraphics();
g.drawImage(inputImg, 0, 0, outputImg.getWidth(), outputImg.getHeight(), 0, 0, inputImg.getWidth(), inputImg.getHeight(), null);
// print
app.print(outputImg, outputImg.getWidth(), outputImg.getHeight());
}
}
Execution produces the following output:
0 1
131072 131073
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
Seems like Graphics2D object works, because I'm able to draw, for example, a line calling the drawLine function. So, I think the inputImg is the source of the issue, but I can't figure out what's wrong.
UPDATE:
I've tried to use AffineTransform, but it didn't help, unfortunately.
Graphics2D g = outputImg.createGraphics();
AffineTransform at = new AffineTransform();
at.setToIdentity();
at.scale(2, 2);
g.drawImage(inputImg, at, null);
To me, this seems to be an issue with the color calculation you're using...
When I change...
inputImg.setRGB(x, y, y * inputWidth * (1 << 16) + x);
to...
int rgb = y * inputWidth * (1 << 16) + x;
inputImg.setRGB(x, y, new Color(rgb).getRGB());
I get a result, albeit a black dot. This suggests to me that by default, your calculation is generating a alpha value of 0
This can be born out in the output that they produce:
My method generates
-16777216 -16777215
-16646144 -16646143
Yours generates
0 1
131072 131073
Now, frankly, this is why I don't do this kind of calculation, not when a API is available to do it for me - but I be dumb ;P
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
public class Main {
public void print(BufferedImage img, int width, int height) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
System.out.print(img.getRGB(x, y) + " ");
}
System.out.println("");
}
}
public static void main(String[] args) {
Main app = new Main();
// create input image
int inputWidth = 2;
int inputHeight = 2;
BufferedImage inputImg = new BufferedImage(inputWidth, inputHeight, BufferedImage.TYPE_INT_ARGB);
// fill input image
System.out.println(inputWidth + "x" + inputHeight);
Color color = Color.RED;
for (int y = 0; y < inputHeight; y++) {
for (int x = 0; x < inputWidth; x++) {
int rgb = y * inputWidth * (1 << 16) + x;
inputImg.setRGB(x, y, new Color(rgb).getRGB());
}
}
JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(inputImg)));
// print
app.print(inputImg, inputWidth, inputHeight);
// create output image
int outputWidth = 4;
int outputHeight = 4;
BufferedImage outputImg = new BufferedImage(outputWidth, outputHeight, BufferedImage.TYPE_INT_ARGB);
// draw inputImg into outputImg
Graphics2D g = outputImg.createGraphics();
g.drawImage(inputImg, 0, 0, outputImg.getWidth(), outputImg.getHeight(), 0, 0, inputImg.getWidth(), inputImg.getHeight(), null);
g.dispose();
JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(outputImg)));
// print
app.print(outputImg, outputImg.getWidth(), outputImg.getHeight());
}
}

How to bend an Image in java

Is there any way to bend a BufferedImage in Java?
I thought that if I crop the image into smaller pieces and rotate them then I would essentially bend the image, but it doesn't seem to work.
Here is the method I created:
/**
* This is a recursive method that will accept an image the point where the bending will start and the point where the bending will end, as well as the angle of bending
*
* #param original:the original image
* #param startingPoint: the point where the bending should start
* #param endingPoint: the point where the bending should end
* #param radiands: the angle
* #return the bent image
*/
public static BufferedImage getBentImage(BufferedImage original, int startingPoint, int endingPoint, double radians) {
if (startingPoint >= endingPoint)
return original;
int type = BufferedImage.TYPE_INT_ARGB;
int width = original.getWidth();
int height = original.getHeight();
BufferedImage crop = original.getSubimage(0, 0, startingPoint, height);
BufferedImage crop0 = original.getSubimage(startingPoint, 0, width - startingPoint, height);
BufferedImage bendCrop = new BufferedImage(width, height, type);
BufferedImage image = new BufferedImage(width, height, type);
AffineTransform rotation = new AffineTransform();
rotation.translate(0, 0);
rotation.rotate(radians);
Graphics2D g = bendCrop.createGraphics();
g.drawImage(crop0, rotation, null);
g.dispose();
g = image.createGraphics();
g.drawImage(crop, 0, 0, null);
g.drawImage(bendCrop, startingPoint, 0, null);
g.dispose();
return getBentImage(image, startingPoint + 1, endingPoint, radians);
}
This is the original Image:
And this is the result of this getBentImage(image, 200, 220, Math.toRadians(1)):
I was expecting something closer to:
Any ideas on how to actually implement a getBentImage() method?
As suggested in the comments, a simple approach is to divide the image into 3 parts:
Identical to the original.
Bent according to the bending transformation.
Constant diagonal continuation.
Here is a quick and a bit messy example that shows the original shape and the resulting shape below it. I just used a label icon for the images instead of doing custom painting. (Also I didn't adhere to the Java naming conventions with final variables because it's math and not typical coding.)
Since there are quite a few variables in the calculation code, I added a sketch at the end that shows what the variables represent.
public class Main extends JFrame {
static BufferedImage image;
public static void main(String[] args) {
try {
image = ImageIO.read(ClassLoader.getSystemResource("img.png"));
} catch (IOException e) {
e.printStackTrace();
}
new Main();
}
public Main() {
getContentPane().setLayout(new BorderLayout(5, 10));
BufferedImage img2 = transform(15, 100, 300);
JLabel label1 = new JLabel(new ImageIcon(image));
label1.setHorizontalAlignment(JLabel.LEFT);
label1.setOpaque(true);
label1.setBackground(Color.YELLOW);
add(label1, BorderLayout.NORTH);
JLabel label2 = new JLabel(new ImageIcon(img2));
label2.setHorizontalAlignment(JLabel.LEFT);
label2.setOpaque(true);
label2.setBackground(Color.CYAN);
add(label2);
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
static BufferedImage transform(int t, int x1, int x2) {
final double TH = Math.toRadians(t);
final int D = x2 - x1;
final int W = image.getWidth();
final int H = image.getHeight();
final int dD = (int) (D / (2 * TH) * Math.sin(2 * TH));
final int dH = (int) (D / TH * Math.pow(Math.sin(TH), 2));
final int pH = (int) ((W - x2) * Math.tan(2 * TH));
final int width = W - (D - dD);
final int height = (int) (H + dH + pH);
System.out.println(W + " " + H + " -> " + width + " " + height);
BufferedImage img2 = new BufferedImage(width, height, image.getType());
for (int x = 0; x < x1; x++) {
for (int y = 0; y < H; y++) {
int rgb = image.getRGB(x, y);
img2.setRGB(x, y, rgb);
}
}
for (int x = x1; x < x2; x++) {
for (int y = 0; y < H; y++) {
int rgb = image.getRGB(x, y);
int dx = (int) (D / (2 * TH) * Math.sin(2 * (x-x1) * TH / D));
int dy = (int) (D / TH * Math.pow(Math.sin((x-x1) * TH / D), 2));
img2.setRGB(x1 + dx, y + dy, rgb);
}
}
for (int x = x2; x < W; x++) {
for (int y = 0; y < H; y++) {
int rgb = image.getRGB(x, y);
int dp = (int) ((x - x2) * Math.tan(2 * TH));
img2.setRGB(x - (D - dD), y + dH + dp, rgb);
}
}
return img2;
}
}
As for the calculations, I'll leave it for you as homework; it's just geometry/trigonometry which belongs on Math.SE more than on SO. If you can't figure it out I'll give you a direction.
Note that this method might not be fast at all and could certainly be optimized, I'll leave that to you also. Oh, and rounding doubles to ints carelessly, so the result is not pixel-perfect.
I dont know what you mean by bending but essentially you have a rectangle and you break one piece of it and rotate it:
so the algorithm is as follows:
rotate line(x, 0, width-1, 0)
rotate line(x, height-1, width-1, height-1)
connect the pieces
So essentially you are looking for rotate line.

Compiling Error: Not a Statement

I am working on a text-based RPG and came across this error while working on it.
package game;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
/**
*
* #author Pyro
*/
public class SpriteSheetLoader {
public int[] sheetPixels;
public int[] pixels;
int x, y, sheetWidth;
public SpriteSheetLoader(BufferedImage sheet){
BufferedImage image = new BufferedImage(sheet.getWidth(), sheet.getHeight(), BufferedImage.TYPE_INT_ARGB);
image.getGraphics().drawImage(sheet, 0, 0, null);
sheetPixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
sheetWidth = sheet.getWidth();
}
public void grabTile(int tile, int width, int height) {
sheetPixels = new int[width * height];
int xTile = tile % 16;
int yTile = tile % 16;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int value = pixels[(x + (y * width))] * sheetPixels[((x + (xTile * width)) + (y + (yTile * height)) * sheetWidth)];
}
}
}
}
The error showed up around the 7th line and I can't seem to figure out the problem
You are not doing any assignment with your code:
pixels[(x + (y * width))] * sheetPixels[((x + (xTile * width)) + (y + (yTile * height)) * sheetWidth)];
You need to do something with the value here, I'm not sure what you are trying to do (like maybe set the value in the array or something) so here is just an example.
int value = pixels[(x + (y * width))] * sheetPixels[((x + (xTile * width)) + (y + (yTile * height)) * sheetWidth)];
In addition to your error, you do sheetPixels = new int[width * height]; and immediately after try to get data out of the array with sheetPixels[...], but that is a whole different problem than the one you asked.

Categories

Resources