Tile seamless is an image operation that is available in GIMP. It transforms the image so that it can cover a surface smoothly with a repeatable pattern. The edges will not be visible and the pieces will fit perfectly when tiled together. It makes senses for grass, floors, walls, etc... As far as the example shown in the GIMP documentation though, it is not good (the Taj Mahal https://docs.gimp.org/2.10/en/gimp-filter-tile-seamless.html) but it gives an idea of how it works.
A transparent layer is applied over the image. It's a translation of half the size of the image (modulo image size) and the closer to the center, the more transparent it gets to show more of the original image. I took a look at the algorithm used in GIMP but it was pretty hard to read (https://gitlab.gnome.org/GNOME/gegl/-/blob/master/operations/common/tile-seamless.c).
So instead I remembered Pythagore to compute the distance to the center.
Then I apply a pro-rata to have an alpha between 0 and 255 :
0 => fully transparent => center
255 => fully opaque => corner
So here is the code :
public class TileSeamless {
public static BufferedImage createSeamlessTile(BufferedImage inputImage) {
int w = inputImage.getWidth();
int h = inputImage.getHeight();
BufferedImage seamlessTile = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
seamlessTile.getGraphics().drawImage(inputImage, 0, 0, null);
seamlessTile.getGraphics().drawImage(createLayerImage(inputImage), 0, 0, null);
return seamlessTile;
}
public static BufferedImage createLayerImage(BufferedImage inputImage) {
int w = inputImage.getWidth();
int h = inputImage.getHeight();
BufferedImage layerImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
Color color = new Color(inputImage.getRGB((x + w / 2) % w, (y + h / 2) % h));
int alpha = (int) Math.round(255 * distanceToCenter(x, y, w, h) / distanceToCenter(0, 0, w, h));
Color newColor = new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
layerImage.setRGB(x, y, newColor.getRGB());
}
}
return layerImage;
}
public static double distanceToCenter(int x, int y, int w, int h) {
double distanceToCenterX = x - w / 2d;
double distanceToCenterY = y - h / 2d;
return Math.sqrt(distanceToCenterX * distanceToCenterX + distanceToCenterY * distanceToCenterY);
}
public static void main(String[] args) throws IOException {
File inputFile = new File(args[0]);
File outputFile = new File(args[1]);
BufferedImage inputImage = ImageIO.read(inputFile);
BufferedImage seamlessTile = TileSeamless.createSeamlessTile(inputImage);
ImageIO.write(seamlessTile, "png", outputFile);
}
The result is not too bad. It looks a bit different from the one produced by GIMP.
It's a bit blury.
Any idea how I can have a slighly more neat result?
Related
I cannot seem to figure out how to draw a transparent and rotated image. I need to be able to draw an image that is transparent and rotated to a certain degree.
I tried this code:
// draws an image that is rotated to a certain degree
public static void drawRotatedImage(BufferedImage image_, int x, int y, int degrees, float scale) {
// graphics used for the utilities of drawing the image (processing)
Graphics2D utilGraphics;
// make rectangular image
int radius = (int) Math.sqrt(image_.getWidth() * image_.getWidth() + image_.getHeight() * image_.getHeight());
BufferedImage image1 = new BufferedImage(radius, radius, BufferedImage.TYPE_INT_RGB);
utilGraphics = image1.createGraphics();
// centers image
utilGraphics.drawImage(image_, image1.getWidth() / 2 - image_.getWidth() / 2, image1.getHeight() / 2 - image_.getHeight() / 2, null);
// scale image
int nw = (int) (image1.getWidth() * scale);
int nh = (int) (image1.getHeight() * scale);
BufferedImage image = new BufferedImage(nw, nh, BufferedImage.TYPE_INT_RGB);
utilGraphics.drawImage(image1, 0, 0, nw, nh, null);
// Rotation information
double rotationRequired = Math.toRadians (degrees);
double locationX = image.getWidth() / 2;
double locationY = image.getHeight() / 2;
AffineTransform tx = AffineTransform.getRotateInstance(rotationRequired, locationX, locationY);
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
ImageProducer filteredImgProd = new FilteredImageSource(op.filter(image, null).getSource(), filter);
Image transparentImg = Toolkit.getDefaultToolkit().createImage(filteredImgProd);
// Drawing the rotated image at the required drawing locations
g2d.drawImage(Toolkit.getDefaultToolkit().createImage(transparentImg.getSource()), x, y, null);
}
The filter variable is defined as:
private static final ImageFilter filter = new RGBImageFilter() {
int transparentColor = new Color(0, 0, 0, 0).getRGB() | 0x0000ffcc;
public final int filterRGB(int x, int y, int rgb) {
if ((rgb | 0x0000ffcc) == transparentColor) {
return 0x0000ffcc & rgb;
} else {
return rgb;
}
}
};
This ...
BufferedImage image = new BufferedImage(nw, nh, BufferedImage.TYPE_INT_RGB);
centeredGraphics.drawImage(image1, 0, 0, nw, nh, null);
You're creating a new BufferedImage (image), but you never actually paint anything to it, instead, you paint image1 to it's own Graphics context.
Now, if you wanted a transparent image, you should have used...
BufferedImage centeredImage = new BufferedImage(radius, radius, BufferedImage.TYPE_INT_ARGB);
instead of...
BufferedImage centeredImage = new BufferedImage(radius, radius, BufferedImage.TYPE_INT_RGB);
And I never used g2d.drawImage(Toolkit.getDefaultToolkit().createImage(transparentImg.getSource()), x, y, null); as it just doesn't make sense to me (transparentImg is already an Image 🤷♂️)
Now, having said all that, I would "suggest" you take each step individually, start by scaling the original image using something like Java: maintaining aspect ratio of JPanel background image and the rotate the image using something like Rotate a buffered image in Java (which will generate a image large enough to contain the rotated image)
Also, if you "create" a Graphics context, you should also dispose of it when you no longer need it, otherwise you could end up with a memory leak.
"Fixed" code...
Just to be clear, I would still recommend sing ARGB instead of RGB for centeredImage as your filter workflow never seemed to work for, but I started with a transparent image anyway
public Image rotateAndScaleImage(BufferedImage originalImage, int degrees, float scale) {
// make rectangular image
int radius = (int) Math.sqrt(originalImage.getWidth() * originalImage.getWidth() + originalImage.getHeight() * originalImage.getHeight());
BufferedImage centeredImage = new BufferedImage(radius, radius, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = centeredImage.createGraphics();
// centers image
int xPos = (centeredImage.getWidth() - originalImage.getWidth()) / 2;
int yPos = (centeredImage.getHeight() - originalImage.getHeight()) / 2;
graphics.drawImage(originalImage, xPos, yPos, null);
graphics.dispose();
// scale image
int nw = (int) (centeredImage.getWidth() * scale);
int nh = (int) (centeredImage.getHeight() * scale);
BufferedImage image = new BufferedImage(nw, nh, BufferedImage.TYPE_INT_RGB);
graphics = image.createGraphics();
// No scaling is done ???
graphics.drawImage(centeredImage, 0, 0, nw, nh, null);
// Rotation information
double rotationRequired = Math.toRadians(degrees);
double locationX = centeredImage.getWidth() / 2;
double locationY = centeredImage.getHeight() / 2;
AffineTransform tx = AffineTransform.getRotateInstance(rotationRequired, locationX, locationY);
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
ImageProducer filteredImgProd = new FilteredImageSource(op.filter(centeredImage, null).getSource(), filter);
Image transparentImg = Toolkit.getDefaultToolkit().createImage(filteredImgProd);
return transparentImg;
}
private static final ImageFilter filter = new RGBImageFilter() {
int transparentColor = new Color(0, 0, 0, 0).getRGB() | 0x0000ffcc;
public final int filterRGB(int x, int y, int rgb) {
if ((rgb | 0x0000ffcc) == transparentColor) {
return 0x0000ffcc & rgb;
} else {
return rgb;
}
}
};
Oh, and I'm returning an Image because I painted directly to a component for testing
Is there some really simple and basic code for making preview for HDR images (like getting 2D BufferedImage output or something)?
I am using this HDR image.
I tried this (it uses TwelveMonkeys), but no success at all (it simply stuck/frozen at ImageReader reader = readers.next();)
I edited it a bit to suit my needs like this, testing where it got broken/stuck/frozen...and it always happen after TEST 1, that is TEST 2 is never reached, tho no IllegalArgumentException is thrown - if I remove the if() section, then TEST 3 is never reached (I am using NetBeansIDE v12.4, Win7 x64):
public BufferedImage hdrToBufferedImage(File hdrFile) throws IOException {
BufferedImage bi = null;
// Create input stream
// I WROTE DOWN THE STRING FOR THIS EXAMPLE, normally it is taken from the hdrFile
// HDR image size is 23.7MB if it matters at all?
ImageInputStream input = ImageIO.createImageInputStream(new File("Z:/HDR/spiaggia_di_mondello_4k.hdr"));
try {
// Get the reader
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
System.err.println("=====>>> TEST 1");
if (!readers.hasNext()) {
throw new IllegalArgumentException("No reader for: " + hdrFile);
}
System.err.println("=====>>> TEST 2");
ImageReader reader = readers.next();
System.err.println("=====>>> TEST 3");
try {
reader.setInput(input);
// Disable default tone mapping
HDRImageReadParam param = (HDRImageReadParam) reader.getDefaultReadParam();
param.setToneMapper(new NullToneMapper());
// Read the image, using settings from param
bi = reader.read(0, param);
} finally {
// Dispose reader in finally block to avoid memory leaks
reader.dispose();
}
} finally {
// Close stream in finally block to avoid resource leaks
input.close();
}
// Get float data
float[] rgb = ((DataBufferFloat) bi.getRaster().getDataBuffer()).getData();
// Convert the image to something easily displayable
BufferedImage converted = new ColorConvertOp(null).filter(bi, new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_INT_RGB));
return converted;
}
Well, if you don't mind occasional extreme halucinogenic oversaturation of some colors here and there (I was unable solving the issue - if anyone knows how to, please, feel free to update my code), you can try this (it is using JavaHDR) + I also added a bit of brightness and contrast to it as all HDR I tested looked too dark for the preview, so if you do not like that you can remove that part from the code:
public int rgbToInteger(int r, int g, int b) {
int rgb = r;
rgb = (rgb << 8) + g;
rgb = (rgb << 8) + b;
return rgb;
}
public BufferedImage hdrToBufferedImage(File hdrFile) throws IOException {
HDRImage hdr = HDREncoder.readHDR(hdrFile, true);
int width = hdr.getWidth();
int height = hdr.getHeight();
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int r = (int) (hdr.getPixelValue(x, y, 0) * 255);
int g = (int) (hdr.getPixelValue(x, y, 1) * 255);
int b = (int) (hdr.getPixelValue(x, y, 2) * 255);
bi.setRGB(x, y, rgbToInteger(r, g, b));
}
}
//***** YOU CAN REMOVE THIS SMALL SECTION IF YOU FEEL THE IMAGE IS TOO BRIGHT FOR YOU
float brightness = 2f;
float contrast = 20f;
RescaleOp rescaleOp = new RescaleOp(brightness, contrast, null);
rescaleOp.filter(bi, bi);
//*****
return bi;
}
I can compile and run the code you posted (changing the path obviously) without problems on my two macOS machines, testing on all the LTS Java versions (8, 11 and 17). In addition, I run code similar to this as part of the CI/CD pipeline of my project that tests on Windows and Linux as well. I think there is something wrong with the setup in your IDE or Java on your computer. I am not able to reproduce the "freeze"-situation you describe...
Here is the output of running the program (I also printed the resulting BufferedImage for verification):
=====>>> TEST 1
=====>>> TEST 2
=====>>> TEST 3
image = BufferedImage#5a42bbf4: type = 1 DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=0 IntegerInterleavedRaster: width = 1024 height = 512 #Bands = 3 xOff = 0 yOff = 0 dataOffset[0] 0
Running with the code as-is (with the NullToneMapper and no post-processing), the image looks like this, due to unnormalized values:
Running with the default/built-in tone mapper, or simply reading the image with ImageIO.read(hdrFile) as suggested in the comments, the image will look like this:
Finally, playing a bit with the code using a custom global tone mapper; param.setToneMapper(new DefaultToneMapper(0.75f)), I get a result like this:
After a long discussion with #HaraldK and his code addition, I am posting the final correct code for this problem, that is in fact mix of #qraqatit code updated a bit with the #HaraldK addition that corrects wrong color tone mapping, here it is:
public int rgbToInteger(int r, int g, int b) {
int rgb = r;
rgb = (rgb << 8) + g;
rgb = (rgb << 8) + b;
return rgb;
}
public BufferedImage hdrToBufferedImage(File hdrFile) throws IOException {
HDRImage hdr = HDREncoder.readHDR(hdrFile, true);
int width = hdr.getWidth();
int height = hdr.getHeight();
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
float colorToneCorrection = 0.75f;
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
float r = hdr.getPixelValue(x, y, 0);
int red = (int) ((r / (colorToneCorrection + r)) * 255);
float g = hdr.getPixelValue(x, y, 1);
int green = (int) ((g / (colorToneCorrection + g)) * 255);
float b = hdr.getPixelValue(x, y, 2);
int blue = (int) (int) ((b / (colorToneCorrection + b)) * 255);
bi.setRGB(x, y, rgbToInteger(red, green, blue));
}
}
//MAKE THE RESULTING IMAGE A BIT BRIGHTER
float brightness = 1.35f;
float contrast = 0f;
RescaleOp rescaleOp = new RescaleOp(brightness, contrast, null);
rescaleOp.filter(bi, bi);
return bi;
}
public static BufferedImage split(BufferedImage img) {
BufferedImage pic = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics g = pic.getGraphics();
int width = 2000/2;
int height = 2000/2;
int imageW = pic.getWidth();
int imageH = pic.getHeight();
// Tile the image to fill our area.
for (int x = 0; x < width; x += imageW) {
for (int y = 0; y < height; y += imageH) {
g.drawImage(pic, x, y, null);
}
}
return pic ;
}
the point of the code is to create a tile of 2x2 of the image (same image reproduce at a smaller size in a 2x2 grid). i want to updated pic so i can print it onto a jpanel. all i get is black image. can someone tell me whats wrong with the code. or tell me how to create a better piece of code.
I want to make four smaller images of the original and place it in a grid of 2x2 that is the same size as the original image
Something like...
public static BufferedImage split(BufferedImage img) {
BufferedImage pic = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics g = pic.getGraphics();
int width = pic.getWidth() / 4;
int height = pic.getHeight() / 4;
Image scaled = img.getScaledInstance(width, height, Image.SCALE_SMOOTH);
// Tile the image to fill our area.
for (int x = 0; x < pic.getWidth(); x += width) {
for (int y = 0; y < pic.getHeight(); y += height) {
g.drawImage(scaled, x, y, null);
}
}
g.dispose();
return pic;
}
You may also like to have a look at Java: maintaining aspect ratio of JPanel background image and Quality of Image after resize very low -- Java for more details about how you can improve the scaling algorithm
private BufferedImage outputImg;
for(int y = 0; y < inputImg.getHeight(); ++y)
{
for(int x = 0; x < inputImg.getWidth(); ++x)
{
Color originPixel = new Color(inputImg.getRGB(x, y));
double X = 0.412453 * originPixel.getRed() + 0.35758 * originPixel.getGreen() + 0.180423 * originPixel.getBlue();
double Y = 0.212671 * originPixel.getRed() + 0.71516 * originPixel.getGreen() + 0.072169 * originPixel.getBlue();
double Z = 0.019334 * originPixel.getRed() + 0.119193 * originPixel.getGreen() + 0.950227 * originPixel.getBlue();
//???
}
}
In color space conversion function I get RGB-pixel and convert it into XYZ-pixel. But how to set this result in outputImg?
Among BufferedImage methods I see only setRGB(int r, int g, int b)
To work with a BufferedImage in a different color model than RGB, you typically have to work with the Raster or DataBuffer directly.
The fastest way to convert from an RGB color space (like sRGB) to an XYZ color space (like CIEXYZ), is to use ColorConvertOp. However, I assume that this is an assignment, and your task is to implement this yourself.
It's possible to create an XYZ BufferedImage like this:
int w = 1024, h = 1024; // or whatever you prefer
ColorSpace xyzCS = ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
ComponentColorModel cm = new ComponentColorModel(xyzCS, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
WritableRaster raster = cm.createCompatibleWritableRaster(w, h);
BufferedImage xyzImage = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
You can then modify the samples/pixels through the WritableRaster, using raster.setPixel(x, y, pixelData) or raster.setPixels(x, y, w, h, pixelData) or one of the raster.setSample(x, y, band, ...)/setSamples(x, y, w, h, band, ...) methods.
You can also get the DataBuffer, using raster.getDataBuffer(), or if you really like to, access the backing array directly:
// The cast is safe, as long as you used DataBuffer.TYPE_BYTE for cm above
DataBufferByte buffer = (DataBufferByte) raster.getDataBuffer();
byte[] pixels = buffer.getData();
I am new to Processing , and have been playing around with its features but can't for the life of me figure out how to resize an image properly. Below is an example of a function where from the tutorials, but I cant seem to resize the image properly so that it fits the window, has anyone any suggestion s ??
import processing.core.*;
public class Adjusting_image_brightness extends PApplet
{
//intiallize image
PImage img;
public void setup()
{
//set the size
size(320,240);
//load the image
img = loadImage("Conn4Board2.jpg");
//img.resize(400, 400);
}
public void draw()
{
//call the pixels
loadPixels();
//run through the pixels
for(int x = 0; x < img.width; x++)
{
for(int y = 0; y <img.height ; y++)
{
//calculate the 1D pixel location
int loc = x + y*width;
//get the R G B values from the picture
float r = red(img.pixels[loc]);
float b= blue(img.pixels[loc]);
float g = green(img.pixels[loc]);
//change the brightness acording to the mouse here
double adjustBrightness = ((float) mouseX / width) * 8.0;
r *= adjustBrightness;
b *= adjustBrightness;
g *= adjustBrightness;
//Constrainr RGB to between 0 - 255
r = constrain(r, 0 , 255);
g = constrain(g, 0 , 255);
b = constrain(b, 0, 255);
//make a new colour and set pixel in the window
int c = color(r,g,b);
pixels[loc] = c;
}
}
updatePixels();
}
}
Thanks
Stephen.
Too much code if all you want is to resize the image so it fits in the window. The image() function already lets you say which bounding box it should fit in, so just use this:
image(img, 0, 0, width, height);
and your image will draw scaled-to-fit.