6

I am receiving a MultipartFile Spring object from rest controller. I am trying to convert any inage file to JPG image but I just need the byte array to save it on mongoDb

I found this code to do that

public boolean convertImageToJPG(InputStream attachedFile) {
    try {
        BufferedImage inputImage = ImageIO.read(attachedFile);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        boolean result = ImageIO.write(inputImage, "jpg", byteArrayOutputStream);
        
        return result;
        
    } catch (IOException e) {
        
        System.err.println("Error " + e);
        
    }
    return false;
}

But result as a false with not error, so ImageIO.write is not working

Also I found this to do the same but using File object, I don't want to create the file on directory, I just need the byte array

public static boolean convertFormat(String inputImagePath,
        String outputImagePath, String formatName) throws IOException {
        FileInputStream inputStream = new FileInputStream(inputImagePath);
        FileOutputStream outputStream = new FileOutputStream(outputImagePath);
         
        // reads input image from file
        BufferedImage inputImage = ImageIO.read(inputStream);
         
        // writes to the output image in specified format
        boolean result = ImageIO.write(inputImage, formatName, outputStream);
         
        // needs to close the streams
        outputStream.close();
        inputStream.close();
         
        return result;
    }

Testing

public class TestImageConverter {
 
    public static void main(String[] args) {
        String inputImage = "D:/Photo/Pic1.jpg";
        String oututImage = "D:/Photo/Pic1.png";
        String formatName = "PNG";
        try {
            boolean result = ImageConverter.convertFormat(inputImage,
                    oututImage, formatName);
            if (result) {
                System.out.println("Image converted successfully.");
            } else {
                System.out.println("Could not convert image.");
            }
        } catch (IOException ex) {
            System.out.println("Error during converting image.");
            ex.printStackTrace();
        }
    }
}

How can I solve my problem?

TuGordoBello
  • 4,350
  • 9
  • 52
  • 78
  • Try "jpeg" instead of "jpg" – Tarik Jul 08 '20 at 18:35
  • @Tarik it didn't works – TuGordoBello Jul 08 '20 at 18:56
  • Are you sure the image is read and decoded successfully? Confirm by printing image size, a couple of pixel values. – Tarik Jul 08 '20 at 19:03
  • @Tarik Spring ``MultipartFile`` is giving me the byte array, size, inputstream, context-type successfully – TuGordoBello Jul 08 '20 at 19:11
  • @Tarik I could converte ``jpg`` file to ``png``but I couldn't the other way – TuGordoBello Jul 08 '20 at 19:23
  • 1
    I did once a script to convert a jpg to jpg (grayscale) and I remember that it was a pain. I had a BufferedImage from ImageIO.read(File f) then I looped over image.height and image.width and extracted the rgb with image.getRGB(x,y) did some calc and then image.setRGB(x,y,rgbaInt). The rgb were in an unexpected order (b/g/r). Could it be that you have to convert rgb to rgba for the png format? It could be that the format for png is actually stored as a/r/g/b. Because this way you could also store it without alpha channel. – F. Müller Jul 08 '20 at 19:46
  • So this question seems to boil down to "Why doesn't ImageIO.write work?", and it **does** work, so the error is clearly somewhere else. Voting to close this one. – Marco13 Jul 09 '20 at 12:46
  • @Marco13 agree, I just receive the PNG file from ``MultipartFile`` object on rest controller, try ``ImageIO.write`` with JPG format and ``write`` return ``false`` – TuGordoBello Jul 09 '20 at 12:56
  • Add a `System.out.println("Have read this: "+inputImage);` after reading the input image. What does it print? – Marco13 Jul 09 '20 at 13:11

1 Answers1

5

UPDATED SOLUTION (alternative with no need for Raster and ColorModel)

It had indeed bothered me that my older solution (see below) still required Rasters and ColorModels. I got challenged on my solution, so I spent some more time looking for alternatives. So the best thing I could come up with now is the following:

try {
    final FileInputStream fileInputStream = new FileInputStream("dice.png");
    final BufferedImage image = ImageIO.read(fileInputStream);
    fileInputStream.close(); // ImageIO.read does not close the input stream

    final BufferedImage convertedImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
    convertedImage.createGraphics().drawImage(image, 0, 0, Color.WHITE, null);

    final FileOutputStream fileOutputStream = new FileOutputStream("dice-test.jpg");
    final boolean canWrite = ImageIO.write(convertedImage, "jpg", fileOutputStream);
    fileOutputStream.close(); // ImageIO.write does not close the output stream

    if (!canWrite) {
        throw new IllegalStateException("Failed to write image.");
    }
} catch (IOException e) {
    e.printStackTrace();
}

I ended up with a copy of the BufferedImage as I did before. It does more or less the same thing, but you can actually reuse the ColorModel and Raster more easily.

drawImage() seems to take care of most of what I did before manually. And since it is standard java library code all the way, it seems indeed to be a better way.

Note that you end up with an Image of type BufferedImage.TYPE_INT_RGB. While it seems to work for the types jpg, png, and gif, I am not sure what will happen to other file formats or files with a different storage ColorModel - information might be lost (e.g. 4 color-channels to 3). For the mentioned types we don't need an alpha channel, even if we convert from gif or jpg to png (it will be Color.WHITE).



OLD SOLUTION

I was not happy with my first design and also it did not quite work the way it should have.

Therefore, I have created one from scratch. I ended up with a little converter for sRGB files. You can convert from png to jpg and vice versa (Edit: Added gif support also). If you want to handle other types feel free to extend this further. You can more or less add it the same way. It might work for other file types as well, but I have not tested them yet. Luckily, it seems that sRGB is quite common though.

Tbh. I have no idea how many combinations and variants (color palettes, precision, quality, b/w, etc.) you can produce or which common properties they share.

Maybe this is good enough for you. Maybe not. At least it was a nice exercise for me.

This solution is by no means perfect. The results looked okay-ish. The file-type conversion worked and the file-size is also smaller than the png.

try {
    final String fileName = "dice.png";
    final BufferedImage inputImage = ImageIO.read(new FileInputStream(fileName));
    final boolean isSRGB = inputImage.getColorModel().getColorSpace().isCS_sRGB();
    final String outputFormat = "gif";
    if (!isSRGB) {
        throw new IllegalArgumentException("Please provide an image that supports sRGB.");
    }
    final WritableRaster raster = createRaster(inputImage);
    final ColorModel colorModel = createColorModel(inputImage);
    final BufferedImage outputImage = new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
    final String outputFileName = fileName + "-converted." + outputFormat;
    final boolean writeResult = ImageIO.write(outputImage, outputFormat, new FileOutputStream(outputFileName));
    if (!writeResult) {
        throw new IllegalStateException("Could not convert file: " + fileName + " to format: " + outputFormat);
    }
    System.out.println(">> Created file: " + outputFileName);
} catch (Exception e) {
    e.printStackTrace();
}
@NotNull
public static ColorModel createColorModel(@NotNull BufferedImage bufferedImage) {
    Objects.requireNonNull(bufferedImage);
    final int type = bufferedImage.getType();
    boolean isAlphaPremultiplied = false;
    int transparency = Transparency.OPAQUE;
    if (type == BufferedImage.TYPE_3BYTE_BGR) {
        isAlphaPremultiplied = true;
    }
    return new ComponentColorModel(
            ColorModel.getRGBdefault().getColorSpace(),
            false, isAlphaPremultiplied, transparency,
            bufferedImage.getData().getDataBuffer().getDataType()
    );
}

@NotNull
public static WritableRaster createRaster(@NotNull BufferedImage bufferedImage) {
    Objects.requireNonNull(bufferedImage);
    final int type = bufferedImage.getType();
    final int width = bufferedImage.getWidth();
    final int height = bufferedImage.getHeight();
    final int pixelStride = 3;
    int[] offset = new int[]{0, 1, 2};
    DataBufferByte dataBufferByte;

    if (type == BufferedImage.TYPE_4BYTE_ABGR || type == BufferedImage.TYPE_BYTE_INDEXED) {
        int dataIndex = 0;
        final byte[] data = new byte[height * width * pixelStride];
        final int bitmask = 0xff;
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                final int rgb = bufferedImage.getRGB(x, y);
                final int blue = bitmask & rgb;
                final int green = bitmask & (rgb >> 8);
                final int red = bitmask & (rgb >> 16);
                if (rgb == 0) {
                    data[dataIndex++] = (byte) bitmask;
                    data[dataIndex++] = (byte) bitmask;
                    data[dataIndex++] = (byte) bitmask;
                } else {
                    data[dataIndex++] = (byte) red;
                    data[dataIndex++] = (byte) green;
                    data[dataIndex++] = (byte) blue;
                }
            }
        }
        dataBufferByte = new DataBufferByte(data, data.length);
    } else if (type == BufferedImage.TYPE_3BYTE_BGR) {
        dataBufferByte = (DataBufferByte) bufferedImage.getRaster().getDataBuffer();
        offset = new int[]{2, 1, 0};
    } else {
        throw new IllegalArgumentException("Cannot create raster for unsupported image type.");
    }

    return Raster.createInterleavedRaster(
            dataBufferByte, width, height,
            pixelStride * width, pixelStride,
            offset,
            null
    );
}

image-conversion-examples

EDIT: Added support for gif.

F. Müller
  • 3,969
  • 8
  • 38
  • 49
  • Seriously, what is the point of this? The question is basically "How to write a JPG with Java", and you certainly do **not** have to cope with all the nitty-gritty details of Color Models and Rasters to do this. Converting a PNG to JPG with Java is done with 2 lines of trivial code, and if it does not work for the asker, then there's clearly a reason that cannot be derived from the question. – Marco13 Jul 09 '20 at 12:48
  • It works prefecctlly, basically I was trying to create a new JPG file in order to work over it – TuGordoBello Jul 09 '20 at 13:21
  • @Marco13 I don't get your point. It does not work out of the box otherwise I would have obviously provided an answer that is out of the box. Instead, I would like to see your attempt on this. – F. Müller Jul 09 '20 at 13:22
  • Btw the problem is with the ColorModel. For some files it just does not work because the image data is sometimes stored in byte[], sometimes in int[] and whatnot the order of the bytes/ints is also important. For example if it is b/g/r the colors are wrongly interpreted because the standard conversion wants srgb to be in a different order. You have be be specific about the format. – F. Müller Jul 09 '20 at 13:38
  • It *should* work out of the box, with `ImageIO.write(ImageIO.read(in), "jpg", out)` as it was already done in the question. If it does not work, I'd ask **why** it does not work (and would ask for an input image where it does not work). In any case: The code that you posted is *not* an adequate solution. Show me an [MCVE] (*including* the input image!) that shows the problem, and I'll show you a better solution. – Marco13 Jul 10 '20 at 12:38
  • Besides, I don't dismiss there might be something I am missing. That is just what I came up with. Also if you research duplicates for this question you will find similar issues. The only workaround I found there was to use another library or to use WriteableRasters and ColorModels. Also I don't like your tone in your comments. Instead of writing comments just provide a solution. – F. Müller Jul 10 '20 at 15:15
  • I prefer the old solution, as I said in the was the ``ImageIO.write`` didn't work for me – TuGordoBello Jul 10 '20 at 16:41
  • In order to notify people about comments, use `@UserName`. The updated solution is basically the solution that I could have posted (and if you don't think that a few lines of generic, efficient, high-level code are better than what you did there with specific ColorModels and pixels, then we can move this to chat and argue about what constitutes a "good" solution in your opinion). Now, we're back at the original question: The asker vaguely says that "~something doesn't work", but doesn't tell us what. This is certainly **not** what stackoverflow is about. – Marco13 Jul 10 '20 at 22:25