Image processing with JavaFX and C; problem getting data from C to Java

48 Views Asked by At

I have a GUI with JavaFX. I get an image from the user. Then i run a c program and giving it the filepath to the picture. The c program processes the picture and give it back to the java program where the processed picture is displayed. Here is the relevant code:

Java:

    public void runCProgram(String t)
    {
        try {
            String command = "C:\\Users\\fendl\\CPP2\\Uebung\\fotofilter";

            // Create a ProcessBuilder for the C program
            ProcessBuilder processBuilder = new ProcessBuilder(command, imagePath, t);

            // Redirect the output to the Java program's output
            processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT);

            // Start the C program
            Process process = processBuilder.start();

            // Wait for the C program to finish
            int exitCode = process.waitFor();

            if (exitCode == 0) {
                // Image processing was successful

                // Read the processed image data from the C program's output stream
                try (InputStream is = process.getInputStream()) {
                    ByteArrayOutputStream outputData = new ByteArrayOutputStream();
                    byte[] buffer = new byte[2056];
                    int bytesRead;
                    while ((bytesRead = is.read(buffer)) != -1) {
                        outputData.write(buffer, 0, bytesRead);
                    }

                    byte[] processedImageData = outputData.toByteArray();
                    
                    Platform.runLater(() -> {
                        imageView.setImage(new Image(new ByteArrayInputStream(processedImageData)));
                    });

                    System.out.println("\n C program successfully processed the image.");
                }
                } else {
                    System.err.println("C program exited with an error code: " + exitCode);
                }
          
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }

C:

int main(int argc, char const *argv[])
{
    FILE *inputFile = fopen(argv[1], "rb");

    if (inputFile == NULL)
    {
        perror("Failed to open the file");
        return 1;
    }


    //The first 54 bytes of a bmp file is the header
    unsigned char imageHeader[54];
    fread(imageHeader, sizeof(unsigned char), 54, inputFile);

    if (imageHeader[0] != 'B' || imageHeader[1] != 'M')
    {
        perror("File is not a bmp file");
        return 1;
    }
    

    int width = *(int *) &imageHeader[18];
    int height = *(int *) &imageHeader[22];
    int bitDepth = *(int *) &imageHeader[28];

    //Normally 3 Bytes per Pixel (red, green, blue)
    int bytesPerPixel = bitDepth / 8;

    //Row of pixel is always a multiple of 4 in a bmp file
    int dataSize = width * height* bytesPerPixel;

    //If there is a color table stored in the bmp file; the next 1024 bytes is the color table
    unsigned char colorTable[1024];
    if (bitDepth <= 8)
    {
        fread(colorTable, sizeof(unsigned char), 1024, inputFile);
    }

    //The rest of the bytes is the data of the image (RGB values for every pixel)
    unsigned char *imageData = (unsigned char *) malloc(dataSize);

    fread(imageData, sizeof(unsigned char), dataSize, inputFile);


    //Apply Filter
    choose_filter(dataSize, bytesPerPixel, imageData, argv[2][0]);

    //Write image to output stream
    fwrite(imageHeader, sizeof(unsigned char), 54, stdout);

    if (bitDepth <= 8)
    {
        fwrite(colorTable, sizeof(unsigned char), 1024, stdout);
    }

    fwrite(imageData, sizeof(unsigned char), dataSize, stdout);

    fflush(stdout);

    fclose(inputFile);

    free(imageData);

    return 0;
}

Problem: The problem is that the "processedImageData" in Java is empty. So there seems to be a problem with how I get the data from my c program.

When writing the image to a file and not to the output stream in C, than everything works fine and we get a correctly processed image. There are also a lot of weird symbol in my console, so the c program seems to return at least something. ChatGPT also couldn't help :(

Does anybody has any idea what the problem could be and how to fix it?

1

There are 1 best solutions below

0
VGR On

1. Move waitFor after the reading of program output.

As DuncG pointed out, you must not call a Process’s waitFor method before reading its output. After calling waitFor, the process is finished and there is no output to read.

Process process = processBuilder.start();
try (InputStream is = process.getInputStream()) {
    // ...
}

// Wait for the C program to finish
int exitCode = process.waitFor();

if (exitCode != 0) {
    System.err.println("C program exited with an error code: " + exitCode);
}

2. Remove call to redirectOutput.

processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT); causes the child process to use the Java program’s standard out as its own standard out. This means the Java program has no way to read that output.

What you want is:

processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);

which will ensure any errors printed by the C program appear on the Java program’s standard error.

3. Check for errors in image loading.

Instead of blindly assuming the image is correct:

imageView.setImage(new Image(new ByteArrayInputStream(processedImageData)));

You should check for errors in the image:

Image image = new Image(new ByteArrayInputStream(processedImageData));
if (image.getException() != null) {
    System.err.println("Exception encountered while loading image.");
    image.getException().printStackTrace();
}

imageView.setImage(image);

4. (Optional) Use readAllBytes instead of a ByteArrayOutputStream.

You don’t need a ByteArrayOutputStream. Just read the bytes using readAllBytes:

try (InputStream is = process.getInputStream()) {
    byte[] processedImageData = is.readAllBytes();

This is not strictly necessary, but it saves memory (fewer copies of the byte array), and having less code helps you eliminate your own code as a possible source of bugs.

5. Calculate dataSize according to the BMP format.

Where you are calculating dataSize, you have this comment:

//Row of pixel is always a multiple of 4 in a bmp file

But you are completely ignoring that statement.

Also, the height can be negative, as explained in the BMP format.

int bytesPerRow = width * bytesPerPixel;
if (bytesPerRow % 4) {
    bytesPerRow += (4 - (bytesPerRow % 4));
}
int dataSize = bytesPerRow * abs(height);

There are probably more compact ways to do it, but my C skills are rusty and I prefer something clear over something cryptic.

6. Calculate color table size according to the BMP format.

The BMP format does not say that the color table is always 1024 bytes. The number of colors is a 32-bit value at offset 46 in the file; if zero, the number of colors is 2ⁿ, where n is the bits per pixel.

int width = *(int *) &imageHeader[18];
int height = *(int *) &imageHeader[22];
int bitDepth = *(int *) &imageHeader[28];
int color_count = *(int *) &imageHeader[46];

// ...

if (color_count == 0)
{
    color_count = 1 << bitDepth;
}
int color_table_size = color_count * 4;

unsigned char *colorTable = NULL;
if (bitDepth <= 8)
{
    colorTable = (unsigned char *) malloc(color_table_size);
    fread(colorTable, sizeof(unsigned char), color_table_size, inputFile);
}

And of course, just before your main method returns:

if (colorTable)
{
    free(colorTable);
}