I haven't experience with processing images in java. My goal is to combine several images. To be more detail, I have a template image and some other images. I want to put those images into template image at specific places.
For e.g:
template image:
specific image:
So, I want to put the dog image onto cats' image places and store the created image.
Please, tell me what is the easiest way to do that?
As Fabian pointed out, identifying patterns mightn't give the expected results, so my suggestion would be an alternative
If you control the templates and provide them to the user as options, you could implement them yourself and populate the images in placeholder nodes. The merged image would come from taking an overall snapshot
I've included a quick example, but note that it's not fully implemented (layout etc) so consider it more as a proof of concept. It's still possible to build on the below to display different images at the same time, text decoration, stars etc to be a closer representation of the example image you provided
This may not be the easiest method, but it could be an enjoyable learning experience. This could also be a viable option since you don't have image processing experience in Java
public class ImageTemplateNode extends Region{
private SimpleObjectProperty<Image> displayedImageProperty;
private ObservableList<Node> children = FXCollections.observableArrayList();
private Random random = new Random();
private int rows, columns;
private final int maximumRotation = 15;
public ImageTemplateNode(int rows, int cols, Image imageToDisplay){
this.rows = rows;
this.columns = cols;
this.displayedImageProperty = new SimpleObjectProperty<>(imageToDisplay);
createDisplayNodes();
setPadding(new Insets(10));
Bindings.bindContentBidirectional(getChildren(), children);
}
public ImageTemplateNode(int rows, int cols, Image imageToDisplay, Image backgroundImage){
this(rows, cols, imageToDisplay);
setBackgroundImage(backgroundImage);
}
private void createDisplayNodes(){
for(int count = 0; count < (rows * columns); count++){
StackPane container = new StackPane();
container.setRotate(getRandomRotationValue());
container.setBackground(
new Background(new BackgroundFill(getRandomColour(), new CornerRadii(5), new Insets(5))));
container.maxWidthProperty().bind(displayedImageProperty.get().widthProperty().add(25));
container.maxHeightProperty().bind(displayedImageProperty.get().heightProperty().add(25));
ImageView displayNode = new ImageView();
displayNode.imageProperty().bind(displayedImageProperty);
displayNode.fitWidthProperty().bind(container.widthProperty().subtract(25));
displayNode.fitHeightProperty().bind(container.heightProperty().subtract(25));
container.getChildren().setAll(displayNode);
children.add(container);
}
}
private int getRandomRotationValue(){
int randomValue = random.nextInt(maximumRotation);
//Rotate clockwise if even, anti-clockwise if odd
return randomValue % 2 == 0 ? randomValue : 360 - randomValue;
}
private Color getRandomColour(){
int red = random.nextInt(256);
int green = random.nextInt(256);
int blue = random.nextInt(256);
return Color.rgb(red, green, blue);
}
#Override
protected void layoutChildren() {
//Calculate the dimensions for the children so that they do not breach the padding and allow for rotation
double cellWidth = (widthProperty().doubleValue()
- getPadding().getLeft() - getPadding().getRight() - maximumRotation) / columns;
double cellHeight = (heightProperty().doubleValue()
- getPadding().getTop() - getPadding().getBottom() - maximumRotation) / rows;
for (int i = 0; i < (rows); i++) {
for (int j = 0; j < (columns); j++) {
if (children.size() <= ((i * (columns)) + j)) {
break;
}
Node childNode = children.get((i * (columns)) + j);
layoutInArea(childNode,
(j * cellWidth) + getPadding().getLeft(),
(i * cellHeight) + getPadding().getTop(), cellWidth, cellHeight,
0.0d, HPos.CENTER, VPos.CENTER);
}
}
}
public void setBackgroundImage(Image backgroundImage){
setBackground(new Background(
new BackgroundImage(backgroundImage,
BackgroundRepeat.REPEAT, BackgroundRepeat.REPEAT, BackgroundPosition.CENTER,
BackgroundSize.DEFAULT)));
}
public void changeDisplayImage(Image newImageToDisplay){
displayedImageProperty.set(newImageToDisplay);
}
public void captureAndSaveDisplay(){
FileChooser fileChooser = new FileChooser();
//Set extension filter
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("png files (*.png)", "*.png"));
//Prompt user to select a file
File file = fileChooser.showSaveDialog(null);
if(file != null){
try {
//Pad the capture area
WritableImage writableImage = new WritableImage((int)getWidth() + 20,
(int)getHeight() + 20);
snapshot(null, writableImage);
RenderedImage renderedImage = SwingFXUtils.fromFXImage(writableImage, null);
//Write the snapshot to the chosen file
ImageIO.write(renderedImage, "png", file);
} catch (IOException ex) { ex.printStackTrace(); }
}
}
}
Screen shots:
Saved snap shots:
Related
I'm trying to test a class which purpose is taking a specification of what to do with a BufferedImage array and proccess it.
I'm concerned about how to aproach this task, it makes no sense to me just duplicate the function code on the test in order to generate a "expected bufferedImage array" and check if the generated BufferedImages are equals to the returned by the class methods.
I've read some slightly similar questions here, but the answers was "charge expected images from file and check with returned ones by class methods" but that sounds real hard to maintain if the proccess changes, new methods are added to the renderer class, it's hard or imposible pregenerate a resulting image or simply the class is refactorized and functions are splitted.
My actual question might be: Is there a little more elegant, "better to maintain" way to do that. I haven't so much experience with unit testing so i'm not sure about how to do this.
EDIT: sorry for the long code, i simplified and translated it. Excuse me if i did a bad translation. english it's not my main language.
public class Renderer extends SwingWorker<BufferedImage[], Integer>
{
private Device device;
private Main main;
private Controller controller;
public static final int FPS = 25;
public Renderer(Device device, Main main, Controller controller)
{
this.device = device;
this.main = main;
this.controller = controller;
}
#Override
protected BufferedImage[] doInBackground() throws Exception
{
// rendering image array
BufferedImage[] output = renderize(main.getActualEscene());
LOG.log.log(Level.INFO, "Rendering");
// getting how many times should repeat
String stringRepeat =
main.getActualEscene().getProperties().get(TextProperties.REPEAT.toString());
int repeat = (stringRepeat == null) ? 1 : Integer.parseInt(stringRepeat);
// getting text speed
String stringFps =
main.getActualEscene().getProperties().get(TextProperties.TEXT_SPEED.toString());
int fps = (stringFps == null) ? FPS : Integer.parseInt(stringFps);
if (!this.isCancelled()) // if this task is not cancelled
{
// we create a pre-viewer
controller.setPreviewer(
new Previewer(controller, repeat, fps, main.getActualEscene()));
SwingUtilities.invokeLater(new Runnable()
{// sincronizing this code with AWT thread
#Override
public void run()
{ // if it's not cancelled
if (controller.getPreviewer() != null
&& !controller.getPreviewer().isCancelled())
{
controller.getPreviewer().execute(); //execute the task
}
}
});
}
return output;
}
/**
* Renders a scene and transforms it in a image array, then save it to the scene
*
* #param scene -> scene
* #return an array with the scene frames
*/
public BufferedImage[] renderize(Scene scene)
{
BufferedImage[] output = null; // end result
BufferedImage[] base = new BufferedImage[1]; // base image
BufferedImage[] animationImages = null; // animation layer
BufferedImage[] textLayer = null; // text layer
BufferedImage[] overLayer = null; // overlayer
// omitted long process to retrieve image properties
// once the text properties are retrieved if it's not null it will be rendered
if (text != null)
{
String backgroundColorString =
scene.getProperties().get(TextProperties.BACKGROUND_COLOR.toString());
int backGroundcolorInt = Integer.parseInt(backgroundColorString);
if (animationImages != null) // if there is an animation layer we create a same size text layer
{
textLayer = new BufferedImage[animationImages.length];
} else
{
textLayer = new BufferedImage[1];
}
for (int i = 0; i < textLayer.length; i++)
{
textLayer[i] = new BufferedImage(device.getWidth(), device.getHeight(),
BufferedImage.TYPE_INT_ARGB);
}
String font = scene.getProperties().get(TextProperties.FONT.toString());
int lettersHeigth = device.getHeight() / 3;
int colorNumber =
Integer.parseInt(scene.getProperties().get(TextProperties.TEXT_COLOR.toString()));
textLayer = addTextToAImage(text, font, lettersHeigth, new Color(colorNumber),
new Color(backGroundcolorInt), textLayer);
}
// has it overlayer?
if (scene.getProperties().containsKey(AnimationProperties.OVER.toString())) // if it has over
{
String over = scene.getProperties().get(AnimationProperties.OVER.toString());
overLayer =
this.readingImagesFromAnimation(OverLibrary.getInstance().getOverByName(over));
}
// mixing layers
output = base; // adding base base
if (animationImages != null) // if there is animation layer we add it
{
output = mixingTwoImages(output, animationImages);
}
if (textLayer != null) // if there is text layer
{
output = mixingTwoImages(output, textLayer);
}
if (overLayer != null) //if there is overlayer
{
output = mixingTwoImages(output, overLayer);
}
// delimiting image operative zone.
output = delimite(output, device);
main.getActualEscene().setPreview(new Preview(output));
LOG.log.log(Level.INFO, "Rendering scene finished: " + scene.getNombre());
return output;
}
/**
* Apply the device mask to an image. Delimiting the scene operative zone.
*
* #param input -> BufferedImage array which need to be delimited
* #param device -> device which it mask need to be applied
* #return An BufferedImage array with a delimited image for the device.
*/
private BufferedImage[] delimite(BufferedImage[] input, Device device)
{
BufferedImage[] output = new BufferedImage[input.length];
for (int i = 0; i < output.length; i++) // copy all original frames
{
output[i] = new BufferedImage(device.getWidth(), device.getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = output[i].createGraphics();
graphics.drawImage(input[i], 0, 0, null);
}
for (int i = 0; i < output.length; i++) // for each frame
{
for (int j = 0; j < output[i].getHeight(); j++) // for each row
{
for (int k = 0; k < output[i].getWidth(); k++) // for each column
{
if (!device.estaEnZonaOperativa(j, k)) // if the coordinate is not in the operative zone
{
int pixel = 0x00000000; // we create a transparent pixel
output[i].setRGB(j, k, pixel); // set the original pixel with this new one
}
}
}
}
return output;
}
/**
* method that reads an animation images and returns it into an array
*
* #param animation -> animaciĆ³n de la que obtendremos sus image
* #return array de BufferedImage con los frames de la animaciĆ³n ordenados por orden de lectura
* ascendente.
*/
private BufferedImage[] readingImagesFromAnimation(Animation animation)
{
BufferedImage[] output = new BufferedImage[animation.getData().length];
for (int i = 0; i < animation.getData().length; i++) // for each frame
{
try
{
File ruta = animation.getData()[i]; // getting its path
output[i] = ImageIO.read(ruta); // reading from the path and save it into output
} catch (IOException e)
{
LOG.log.log(Level.SEVERE, "error reading animation file: " + animation.getData()[i],
e);
}
}
return output;
}
/**
* create a text layer an add it to the image parameter center
*
* #param text -> text to include
* #param font -> font of the text
* #param size -> size
* #param color -> text color
* #param image -> image you want to add text
* #return a text layer that need to be mixed with original one.
*/
private BufferedImage[] addTextToAImage(String text, String font, int size, Color color,
Color backgroundColor, BufferedImage[] image)
{
// we set the size of the output as the same of the original image
BufferedImage[] output = new BufferedImage[image.length];
for (int i = 0; i < image.length; i++) // for each frame
{
output[i] = new BufferedImage(image[i].getWidth(), image[i].getHeight(),
BufferedImage.TYPE_INT_ARGB); // we create a single new image
Graphics2D graphics = output[i].createGraphics(); // get its graphics
// calculate for metrics
Font font = new Font(font, Font.PLAIN, size); // create a font
graphics.setFont(font); // setting it into the graphics
FontMetrics fontMetrics = graphics.getFontMetrics(font); // generating metric object
// calculate the text coordinates
int x = (image[i].getWidth() - fontMetrics.stringWidth(text)) / 2;
int y = ((image[i].getHeight() - fontMetrics.getAscent() - fontMetrics.getDescent()) / 2)
+ fontMetrics.getAscent();
int width = fontMetrics.stringWidth(text); // usefull metric to set a brackground
int height = fontMetrics.getAscent() + fontMetrics.getDescent();
// setting a background
graphics.setColor(backgroundColor);
graphics.fill(new Rectangle(x, y - fontMetrics.getAscent(), width, height));
// drawing text
graphics.setColor(color); // setting a color for text
graphics.drawString(text, x, y);
}
return output;
}
/**
* method to join two bufferedImage arrays and mix them. The shortest array will be played on loop
*
* WARNIN: the images needs to has the same resolution
*
* #param image -> image mixed as background
* #param image2 -> image mixed as foreground
* #return a mixed image array.
*/
private BufferedImage[] mixingTwoImages(BufferedImage[] image, BufferedImage[] image2)
{
// checking no empty images.
if (image.length == 0 || image2.length == 0)
{
RuntimeException exception =
new RuntimeException("empty images");
LOG.log.log(Level.SEVERE, exception.getMessage(), exception);
throw exception;
}
int width = Math.max(image[0].getWidth(), image2[0].getWidth());
int height = Math.max(image[0].getHeight(), image2[0].getHeight());
// creating a sequence of the longest size of them
BufferedImage[] output = new BufferedImage[Math.max(image.length, image2.length)];
for (int i = 0; i < output.length; i++) // for each frame
{
// we create a frame
output[i] = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics g = output[i].getGraphics();
// mixing the frame
if (image.length > image2.length) // if the first array is longer
{
g.drawImage(image[i], 0, 0, null);
g.drawImage(image2[i % image2.length], 0, 0, null);
} else if (image.length < image2.length) // if the second array is longer
{
g.drawImage(image[i % image.length], 0, 0, null);
g.drawImage(image2[i], 0, 0, null);
} else // bot of the same size
{
g.drawImage(image[i], 0, 0, null);
g.drawImage(image2[i], 0, 0, null);
}
}
return output;
}
I've read about Convolution Filters recently and decided to try it out. I wanted to make code that blurs the image but what it ends up doing is brightening it. I've been looking at my code for some time now and can't find any mistakes. Could anyone help?
Here is my code:
final static int filterHeight =3;
final static int filterWidth = 3;
static double filter[][] = new double[][]{
{1,1,1},
{1,1,1},
{1,1,1}
};
public static void main(String[] args) {
BufferedImage img;
BufferedImage result;
try
{ File in = new File("in.jpg");
File out = new File("out.jpg");
img = ImageIO.read(in);
Color[][] pixels = new Color[img.getWidth()][img.getHeight()];
for(int i=0;i<img.getWidth();i++){
for(int j=0;j<img.getHeight();j++){
pixels[i][j]=new Color(img.getRGB(i,j),true);
}
}
result = new BufferedImage(img.getWidth(), img.getHeight(), img.getType());
for(int x=0;x<img.getWidth();x++){
for(int y=0;y<img.getHeight();y++){
int r=0,g=0,b=0;
for(int i=0;i<filterWidth;i++){
for(int j=0;j<filterHeight;j++){
int imageX = (int)(x - filterWidth / 2 + i + img.getWidth()) % img.getWidth();
int imageY = (int)(y - filterHeight / 2 + j + img.getHeight()) % img.getHeight();
if(imageX<0 || imageY<0) System.out.println("ERROR: "+imageX+" "+imageY);
r+=pixels[imageX][imageY].getRed()*filter[i][j];
g+=pixels[imageX][imageY].getGreen()*filter[i][j];
b+=pixels[imageX][imageY].getBlue()*filter[i][j];
}
if(r>255) r=255;
if(r<0) r=0;
if(g>255) g=255;
if(g<0) g=0;
if(b>255) b=255;
if(b<0) b=0;
Color color = new Color(img.getRGB(x,y),true)
Color colorBlur = new Color(r,g,b,color.getAlpha());
result.setRGB(x, y, colorBlur.getRGB());
}
}
}
ImageIO.write(result, "JPG", out );
}
catch (IOException e)
{
e.printStackTrace();
}
And here is image before aplying filter:
And after:
There are two options in order to get the result you desire.
Either you create a filter matrix with a sum of 1 (Like #Spektre mentioned in his comment above) or multiply the pixel's new value with a factor of 1 / sum(filterMatrix).
For a nice beginner tutorial of the concept of blurring I would recommend:
Concept of Blurring - www.TutorialsPoint.com
I'm trying to load a video and then display it in a pixelated manner. It worked one time after loading for very long time, but then it stopped working - just a black screen and nothing comes up and without error message I wonder what goes wrong. Thanks.
import processing.video.*;
Movie movie;
int videoScale = 8;
int cols, rows;
void setup() {
size(640, 360);
background(0);
movie = new Movie(this, "movie.mp4");
movie.loop();
cols = width / videoScale;
rows = height / videoScale;
}
void draw() {
movie.loadPixels();
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
int x = i * videoScale;
int y = j * videoScale;
color c = movie.pixels[i + j * movie.width];
fill(c);
noStroke();
rect(x, y, videoScale, videoScale);
}
}
}
// Called every time a new frame is available to read
void movieEvent(Movie movie) {
movie.read();
}
You may be sampling from the wrong place here:
color c = movie.pixels[i + j * movie.width];
First off, i is your cols counter, which is the x dimension, the j is the rows counter, y dimension.
Secondly, you probably want to sample at the same scale, and therefore need to multiply by videoScale. You already have the x,y variables for that, so try sampling like this:
color c = movie.pixels[y * movie.width + x];
Alternatively, you can use a PGraphics instance as a frame buffer to draw into at a smaller scale (resample), then draw the small buffer at a larger scale:
import processing.video.*;
Movie movie;
int videoScale = 8;
int cols, rows;
PGraphics resized;
void setup() {
size(640, 360);
background(0);
noSmooth();//remove aliasing
movie = new Movie(this, "transit.mov");
movie.loop();
cols = width / videoScale;
rows = height / videoScale;
//setup a smaller sized buffer to draw into
resized = createGraphics(cols, rows);
resized.beginDraw();
resized.noSmooth();//remove aliasing
resized.endDraw();
}
void draw() {
//draw video resized smaller into a buffer
resized.beginDraw();
resized.image(movie,0,0,cols,rows);
resized.endDraw();
//draw the small buffer resized bigger
image(resized,0,0,movie.width,movie.height);
}
// Called every time a new frame is available to read
void movieEvent(Movie movie) {
movie.read();
}
I'm writing an application to generate QR codes with custom dot shapes. What's the best way to do this using zxing?
So far, I've dug through the source code and I see that the data bits are written in com.google.zxing.qrcode.encoder.MatrixUtil.embedDataBits(). I think I could add some code on to the end of this function which would allow me to mask the dots but I'm not sure how to do this in Java. I can't extend the class because it's declared as final. Would it be a good idea and if so how would I extend this method in that way?
The other option I've been looking at involves post-processing the image produced by QRCode but this is really complex I think as I'd have to find a way to discern the dots from the positioning squares.
Is there a better way to do what I'm looking to do? Is there another QR code library besides zxing which can do what I'm looking to do out of the box?
P.S. I want to note that this is not a duplicate of this question although the keywords are similar.
The following java code uses zxing to make a QR-code image with circular dots and a circular finder pattern (custom rendering style). This can be adapted to other custom render styles.
I use the Encoder class directly and bypass QRCodeWriter and MatrixToImageWriter to gain enough control to alter the rendering. To alter the finder pattern, I use the fact that the finder pattern is always 7 dots wide/tall. Otherwise I would have to create a custom version of MatrixUtil (and perhaps Encoder).
Example QR Code Image Generated:
public static void main(String[] args) {
try {
generateQRCodeImage("https://www.google.com", 300, 300, "./MyQRCode.png");
} catch (Exception e) {
e.printStackTrace();
}
}
private static void generateQRCodeImage(String text, int width, int height, String filePath) throws WriterException, IOException {
final Map<EncodeHintType, Object> encodingHints = new HashMap<>();
encodingHints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
QRCode code = Encoder.encode(text, ErrorCorrectionLevel.H, encodingHints);
BufferedImage image = renderQRImage(code, width, height, 4);
try (FileOutputStream stream = new FileOutputStream(filePath)) {
stream.write(bufferedImageToBytes(image));
}
}
private static BufferedImage renderQRImage(QRCode code, int width, int height, int quietZone) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = image.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setBackground(Color.white);
graphics.clearRect(0, 0, width, height);
graphics.setColor(Color.black);
ByteMatrix input = code.getMatrix();
if (input == null) {
throw new IllegalStateException();
}
int inputWidth = input.getWidth();
int inputHeight = input.getHeight();
int qrWidth = inputWidth + (quietZone * 2);
int qrHeight = inputHeight + (quietZone * 2);
int outputWidth = Math.max(width, qrWidth);
int outputHeight = Math.max(height, qrHeight);
int multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight);
int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
int topPadding = (outputHeight - (inputHeight * multiple)) / 2;
final int FINDER_PATTERN_SIZE = 7;
final float CIRCLE_SCALE_DOWN_FACTOR = 21f/30f;
int circleSize = (int) (multiple * CIRCLE_SCALE_DOWN_FACTOR);
for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) {
for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) {
if (input.get(inputX, inputY) == 1) {
if (!(inputX <= FINDER_PATTERN_SIZE && inputY <= FINDER_PATTERN_SIZE ||
inputX >= inputWidth - FINDER_PATTERN_SIZE && inputY <= FINDER_PATTERN_SIZE ||
inputX <= FINDER_PATTERN_SIZE && inputY >= inputHeight - FINDER_PATTERN_SIZE)) {
graphics.fillOval(outputX, outputY, circleSize, circleSize);
}
}
}
}
int circleDiameter = multiple * FINDER_PATTERN_SIZE;
drawFinderPatternCircleStyle(graphics, leftPadding, topPadding, circleDiameter);
drawFinderPatternCircleStyle(graphics, leftPadding + (inputWidth - FINDER_PATTERN_SIZE) * multiple, topPadding, circleDiameter);
drawFinderPatternCircleStyle(graphics, leftPadding, topPadding + (inputHeight - FINDER_PATTERN_SIZE) * multiple, circleDiameter);
return image;
}
private static void drawFinderPatternCircleStyle(Graphics2D graphics, int x, int y, int circleDiameter) {
final int WHITE_CIRCLE_DIAMETER = circleDiameter*5/7;
final int WHITE_CIRCLE_OFFSET = circleDiameter/7;
final int MIDDLE_DOT_DIAMETER = circleDiameter*3/7;
final int MIDDLE_DOT_OFFSET = circleDiameter*2/7;
graphics.setColor(Color.black);
graphics.fillOval(x, y, circleDiameter, circleDiameter);
graphics.setColor(Color.white);
graphics.fillOval(x + WHITE_CIRCLE_OFFSET, y + WHITE_CIRCLE_OFFSET, WHITE_CIRCLE_DIAMETER, WHITE_CIRCLE_DIAMETER);
graphics.setColor(Color.black);
graphics.fillOval(x + MIDDLE_DOT_OFFSET, y + MIDDLE_DOT_OFFSET, MIDDLE_DOT_DIAMETER, MIDDLE_DOT_DIAMETER);
}
Maven dependency:
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.4.0</version>
</dependency>
I ended up forking zxing and using JitPack to include it with Maven. I implemented functionality for RGB masking and for drawing circles and outlined squares as dot shapes. Here is the repository: https://github.com/flotwig/zxing
Using the ImageJ api, I'm trying to save an composite image, made up of several images laid out side by side.
I've got code that loads ImagePlus objs, and saves them. But I can't figure how to paste an image into another image.
I interpret the problem as taking multiple images and stitching them together side by side to form a large one where the images may have different dimensions. The following incomplete code is one way of doing it and should get you started.
public ImagePlus composeImages(ArrayList<ImagePlus> imageList){
int sumWidth = 0;
int maxHeight = 0;
for(ImagePlus imp : imageList){
sumWidth = sumWidth +imp.getWidth();
if(imp.getHeight() > maxHeight)
maxHeight = imp.getWidth();
}
ImagePlus impComposite = new ImagePlus();
ImageProcessor ipComposite = new ShortProcessor(sumWidth, maxHeight);
for(int i=0; i<sumWidth; i++){
for(int j=0; j<sumWidth; j++){
ipComposite.putPixelValue(i, j, figureOutThis);
}
}
impComposite.setProcessor(ipComposite);
return impComposite;
}
You need to write an algorithm to find the pixel value (figureOutThis) to put in the composite image at i,j. That is pretty trivial if all images have the same width and a little bit more work otherwise. Happy coding
Edit:
I should add that I am assuming they are also all short images (I work with medical grayscale). You can modify this for other processors
This code combines/stitches a grid of images:
It assumes the images are all of the same dimensions.
ImagePlus combine(List<List<ImagePlus>> imagesGrid) {
checkArgument(
!imagesGrid.isEmpty() && !imagesGrid.get(0).isEmpty(), "Expected grid to be non-empty");
checkArgument(
imagesGrid.stream().map(List::size).distinct().count() == 1,
"Expected all rows in the grid to be of the same size");
checkArgument(
imagesGrid.stream().flatMap(List::stream).map(ImagePlus::getWidth).distinct().count() == 1,
"Expected all images to have the same width");
checkArgument(
imagesGrid.stream().flatMap(List::stream).map(ImagePlus::getHeight).distinct().count() == 1,
"Expected all images to have the same height");
int rows = imagesGrid.size();
int cols = imagesGrid.get(0).size();
int singleWidth = imagesGrid.get(0).get(0).getWidth();
int singleHeight = imagesGrid.get(0).get(0).getHeight();
int combinedWidth = singleWidth * cols;
int combinedHeight = singleHeight * rows;
ImageProcessor processor = new ColorProcessor(combinedWidth, combinedHeight);
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
ImagePlus image = imagesGrid.get(row).get(col);
int offSetWidth = col * singleWidth;
int offsetHeight = row * singleHeight;
for (int w = 0; w < singleWidth; w++) {
for (int h = 0; h < singleHeight; h++) {
processor.putPixel(w + offSetWidth, h + offsetHeight, image.getPixel(w, h));
}
}
}
}
ImagePlus combinedImage = new ImagePlus();
combinedImage.setProcessor(processor);
return combinedImage;
}