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?
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
);
}
EDIT: Added support for gif.
Related
i only got the image data with NO header informations but i know several things like:
16 bit grayscale data
1200x1200 (although its 1200x900 but its likely to have a "bar" at the buttom)
the data are 2880000 bytes in size which fits 1200x1200 x 2bytes ->short
here is the raw image data
for visualizing i use this:
public static void saveImage(short[] pix, int width, int height, File outputfile) {
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
int[] nBits = {16};
ComponentColorModel cm = new ComponentColorModel(cs, nBits,false, false, Transparency.OPAQUE, DataBuffer.TYPE_USHORT);
SampleModel sm = cm.createCompatibleSampleModel(width, height);
DataBufferShort db = new DataBufferShort(pix, width*height);
WritableRaster raster = Raster.createWritableRaster(sm, db, null);
BufferedImage bf = new BufferedImage(cm, raster, false, null);
if(outputfile!=null)
try {
if(!ImageIO.write(bf, "png", outputfile)) System.out.println("No writer found.");
System.out.println("Saved: "+outputfile.getAbsolutePath());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
else System.out.println("error");
}
The data are read like this (only experimental/bad code, its only for testing):
for(int tt=1; tt<20; tt++) {
pix = new short[1200*1200];
i = 0;
int z = 0;
int line = 0;
//loop
while(i<(1200*1200)) {
pix[i++] = buffer.getShort(z);
z += tt;
if(z>=(len-1)) {
line += 2;
z = line;
if(z>=(len-1)) {
System.out.println("break at "+z);
break;
}
System.out.println("test "+line);
}
}
System.out.println("img_"+imgcount+".png "+pix.length);
saveImage(pix, 1200, 1200, new File("img_"+imgcount+"_"+tt+".png"));
}
Where i can see something for tt=4,8,16 (images get multiplied) but i cant realy get the whole picture.image tt=8 image tt=16
Its like the solution is in front of me but i cant see it xD
Can someone help me with the algorithm/format the image is stored?
EDIT: Reading data consecutively with:
short[] pix = new short[1200*1200];
int i = 0;
while(i< (1200*1200) && buffer.remaining()>0) {
pix[i++] = buffer.getShort();
}
results in: noisy picture
EDIT 2:
Ok looks like its base64 encoded which makes sense due its stored in a xml file
I finaly solved it, its base64 encoded and little endian (thanks RealSkeptic for hinting to try little/big endian).
I've written a small application for personal use in macOs using java (the only language I know) that reads a directory and compare the images found in it with every other image.
Edit: As user dbush pointed out, here is the code I should've posted:
for (int i = 0; i < files.size(); i++) {
file1 = files.get(i).getAbsolutePath();
for (int j = i + 1; j < files.size(); j++) {
file2 = files.get(j).getAbsolutePath();
System.out.println("Comparing " + files.get(i).getName() + sp + files.get(j).getName());
System.out.println(Arrays.equals(extractBytes(file1), extractBytes(file2)));
}
}
private static byte[] extractBytes(String imageName) throws IOException {
// open image
File imgPath = new File(imageName);
BufferedImage bufferedImage = getSample(imgPath);
// get DataBufferBytes from Raster
WritableRaster raster = bufferedImage.getRaster();
DataBufferByte data = (DataBufferByte) raster.getDataBuffer();
return (data.getData());
}
private static BufferedImage getSample(File f) {
BufferedImage img = null;
Rectangle sourceRegion = new Rectangle(0, 0, 10, 10); // The region you want to extract
try {
ImageInputStream stream1 = ImageIO.createImageInputStream(f); // File or input stream
Iterator<ImageReader> readers = ImageIO.getImageReaders(stream1);
if (readers.hasNext()) {
ImageReader reader = readers.next();
reader.setInput(stream1);
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(sourceRegion); // Set region
img = reader.read(0, param); // Will read only the region specified
}
} catch (IOException e) {
System.out.println(e);
}
return img;
}
It involves reading two images in a byte array and comparing both. The problem is that it takes a long time to do this, even if I sample a small portion of each image.
Should I re-write the same application in C or swift for better performance, considering I'll be running it on macOs from the terminal? Or it will do minimal difference.
Thanks a lot in advance!
I am trying to compare an embedded image located in a MP3 file with the exact same image saved as a JPG. If the images are identical then I would like to perform some further processing, however, when I compare the 2 images (RGB comparison) I keep getting false.
I am sure that the images are identical because I extracted the image from the same MP3 to originally create the JPG using the following code.
Artwork aw = tag.getFirstArtwork();
ByteArrayInputStream bis = new ByteArrayInputStream(aw.getBinaryData());
BufferedImage imgA = ImageIO.read(bis);
File outputfile = new File("expectedImage.jpg");
ImageIO.write(imgA, "jpg", outputfile);
After I ran that to get the image I just commented out that section, now I have the following code in place to compare the MP3 embedded image with the JPG
Extract the MP3 image and call the comparison method
try {
Artwork aw = tag.getFirstArtwork();
ByteArrayInputStream bis = new ByteArrayInputStream(aw.getBinaryData());
BufferedImage imgA = ImageIO.read(bis);
File expectedImageFile = new File("expectedImage.jpg");
BufferedImage imgB = ImageIO.read(expectedImageFile);
if(compareImages(imgA, imgB)) {
System.out.println("The Images Match.");
}else {
System.out.println("The Images Do Not Match.");
}
}
catch (IOException e) {
e.printStackTrace();
}
Compare The Images
The method fails when comparing the pixels for equality on the first pass through the loop.
public static boolean compareImages(BufferedImage imgA, BufferedImage imgB) {
// The images must be the same size.
if (imgA.getWidth() != imgB.getWidth() || imgA.getHeight() != imgB.getHeight()) {
return false;
}
int width = imgA.getWidth();
int height = imgA.getHeight();
// Loop over every pixel.
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// Compare the pixels for equality.
if (imgA.getRGB(x, y) != imgB.getRGB(x, y)) {
return false;
}
}
}
return true;
}
I try you code and read the #Sami Kuhmonen comment and I understand.
When you use ImageIO.write(imgA, "jpg", outputfile) you pass by an other encoder and you can loss data.
You need change this by the standard technique.
Example [UPDATED]
public static void main(String[] args) {
try {
//Write the picture to BAOS
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
byteBuffer.write(aw.getBinaryData(), 0, 1);
//New file target
File outputfile = new File("expectedImage.jpg");
OutputStream outputStream = new FileOutputStream(outputfile);
byteBuffer.writeTo(outputStream);
outputStream.close();
File expectedImageFile = new File("expectedImage.jpg");
ByteArrayInputStream bis = new ByteArrayInputStream(aw.getBinaryData());
BufferedImage imgA = ImageIO.read(bis);
FileInputStream fis = new FileInputStream(expectedImageFile);
BufferedImage imgB = ImageIO.read(fis);
if(compareImages(imgA, imgB)) {
System.out.println("The Images Match.");
}else {
System.out.println("The Images Do Not Match.");
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static boolean compareImages(BufferedImage imgA, BufferedImage imgB) {
// The images must be the same size.
if (imgA.getWidth() != imgB.getWidth() || imgA.getHeight() != imgB.getHeight()) {
return false;
}
int width = imgA.getWidth();
int height = imgA.getHeight();
// Loop over every pixel.
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// Compare the pixels for equality.
if (imgA.getRGB(x, y) != imgB.getRGB(x, y)) {
return false;
}
}
}
return true;
}
I've been a little busy lately, but I finally got a chance to look back into this today and based on what was said above I figured that the main issue was that the image was being compressed in one case and in the other it wasn't. I am sure there is a better solution but a quick dirty fix was to just save a temporary JPEG version of the image that I would like to check for, then do the comparison using 2 buffered images, one that reads in the JPEG file I have saved to the directory and one that reads in the temp JPEG I just created and when the test is complete just use the File.delete() to remove the temp file.
Extract the MP3 image and call the comparison method
The Compare method is the same as originally stated
Artwork aw = tag.getFirstArtwork();
ByteArrayInputStream bis = new ByteArrayInputStream(aw.getBinaryData());
BufferedImage tempImg = ImageIO.read(bis);
File tempFile = new File("temp.jpg");
ImageIO.write(tempImg, "jpg", tempFile);
BufferedImage imgA = ImageIO.read(tempFile);
File expectedImageFile = new File("imgToCheckAgainst.jpg");
BufferedImage imgB = ImageIO.read(expectedImageFile);
if(compareImages(imgA, imgB)) {
System.out.println("The Images Match");
}else {
System.out.println("The images do not match.");
}
tempFile.delete();
I built a little java program that hides messages in an image using the least significant bit method. It works fine when inputting a jpg file. The output may be png or jpg. When inputting a png though, the result looks very stange.
Here are the original and the result images respectively:
public abstract class Builder{
public static void leastSignificantBitEncryption(String imageSource, String message, String newPath) {
BufferedImage image = returnImage(imageSource);
//prepare variables
String[] messageBinString = null;
String[] pixelBinString = null;
final byte[] messageBin = message.getBytes(StandardCharsets.UTF_8);
final byte[] pixelsBin = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
//convert message and image to binary string array
try {
messageBinString = stringToBinaryStrings(messageBin);
pixelBinString = stringToBinaryStrings(pixelsBin);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String[] messageBinStringCut = splitIn2Bit(messageBinString); //split message binary into 2 bit strings
String[] pixelBinStringNew = pixelBinString.clone(); //insert 2 bit strings in last 2 bits of bytes from bitmap
insert2Bit(messageBinStringCut, pixelBinStringNew);
byte[] pixelsBinNew = stringArrayToByteArray(pixelBinStringNew); //Convert string array to byte array
try { //Create new image out of bitmap
int w = image.getWidth();
int h = image.getHeight();
BufferedImage imageNew = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR);
imageNew.setData(Raster.createRaster(imageNew.getSampleModel(), new DataBufferByte(pixelsBinNew, pixelsBinNew.length), new Point()));
File imageFile = new File(newPath);
ImageIO.write(imageNew, "png", imageFile);
} catch (IOException e) {
e.printStackTrace();
}
}
private static String[] stringToBinaryStrings(byte[] messageBin) throws UnsupportedEncodingException{
String[] bytes = new String[messageBin.length];
int i = 0;
for(byte b : messageBin) {
bytes[i] = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
i++;
}
return bytes;
}
private static String binaryStringsToString(String[] messageBin) throws UnsupportedEncodingException{
StringBuilder stringBuilder = new StringBuilder();
int i = 0;
while(messageBin[i] != null) {
stringBuilder.append((char) Integer.parseInt(messageBin[i], 2));
i++;
}
return stringBuilder.toString();
}
private static BufferedImage returnImage(String imageSource) {
try{
try {
return ImageIO.read(new URL(imageSource));
} catch (MalformedURLException e) {
return ImageIO.read(new File(imageSource));
}
} catch (IOException ioe) {
ioe.printStackTrace();
return null;
}
}
private static byte[] stringArrayToByteArray(String[] stringArray) {
byte[] byteArray = new byte[stringArray.length];
for(int i = 0; i < stringArray.length; i++) {
byteArray[i] = (byte) Integer.parseInt(stringArray[i], 2);
}
return byteArray;
}
private static String[] splitIn2Bit(String[] inputArray) {
String[] outputArray = new String[inputArray.length * 4];
for(int i = 0; i < outputArray.length; i += 4) {
String[] splitByte = inputArray[i / 4].split("(?<=\\G..)");
outputArray[i] = splitByte[0];
outputArray[i + 1] = splitByte[1];
outputArray[i + 2] = splitByte[2];
outputArray[i + 3] = splitByte[3];
}
return outputArray;
}
private static String[] insert2Bit(String[] twoBitArray, String[] insertArray) {
for(int i = 0; i < twoBitArray.length; i++) {
insertArray[i] = insertArray[i].substring(0, 6) + twoBitArray[i];
}
return insertArray;
}
}
Also, the testclass:
public class Test {
public static void main(String[] args) {
Builder.leastSignificantBitEncryption("IMAGEPATH OR URL", "MESSAGE", "PATH FOR IMAGE CONTAINING MESSAGE");
Builder.leastSignificantBitDecryption("PATH OF IMAGE CONTAINING MESSAGE", "PATH FOR TXT CONTAINING OUTPUT");
}
}
The error originates from the fact that the png image has an extra channel for transparency. System.out.println(pixelsBin.length); returns 338355 bytes for the jpg and 451140 bytes for the png.
The simplest solution would be to create the appropriate imageNew depending on the format file. For example,
int w = image.getWidth();
int h = image.getHeight();
BufferedImage imageNew = null;
if (imageSource.matches(".*jpg$")) {
imageNew = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR);
} else if (imageSource.matches(".*png$")) {
imageNew = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
} else {
// whatever
}
imageNew.setData(Raster.createRaster(imageNew.getSampleModel(), new DataBufferByte(pixelsBinNew, pixelsBinNew.length), new Point()));
However, you have to be aware that the message is not embedded in both types in the same pixels. The byte array of a 3-channel image (no transparency) goes like this
first-pixel-BLUE, first-pixel-GREEN, first-pixel-RED, second-pixel-BLUE, etc
while for a 4-channel image
first-pixel-ALPHA, first-pixel-BLUE, first-pixel-GREEN, first-pixel-RED, second-pixel-ALPHA, etc
If you care about that detail, you might be interested in removing the alpha channel from the png first, so you're always working with 3-channel images.
So I try to create an image from a byte array, but I can't figure out why the ImageIO.read() method returns a null pointer without any exception.
#Override
public int setParam(byte[] buffer) {
mFlag = buffer[0]; //TODO
mX = Convertor.convert2BytesToInt(buffer[1], buffer[2]);
mY = Convertor.convert2BytesToInt(buffer[3], buffer[4]);
mWidth = Convertor.convert2BytesToInt(buffer[5], buffer[6]);
mHeight = Convertor.convert2BytesToInt(buffer[7], buffer[8]);
mLength = Convertor.convert4BytesToInt(buffer[9], buffer[10], buffer[11], buffer[12]);
byte[] bufferpix = Arrays.copyOfRange(buffer, 13, 13+mLength);
ByteArrayInputStream in = new ByteArrayInputStream(bufferpix);
try {
mImage = ImageIO.read(in);
} catch (IOException e) {
e.printStackTrace();
}
return 13+mLength;
}
#Override
public void draw(Graphics2D g, ArrayList<Color> palette) {
System.out.print("Draw Image\n");
g.drawImage(mImage, mX, mY, mWidth, mHeight, null);
}
The buffer seems to be okay, it contains data RGBA (1 byte for each, so 4 bytes per pixels).
Do you see any problem with that usage?
Thx
Btw, if you wonder, this buffer has previously been created by the Android class Bitmap.
I wasn't using the right method:
int[] bufferpix = new int[mLength];
for(int i=0; i<mLength;i++){
bufferpix[i] = buffer[i+13];
}
mImage = new BufferedImage(mWidth, mHeight, BufferedImage.TYPE_4BYTE_ABGR_PRE);
mImage.getRaster().setPixels(0, 0, mWidth, mHeight, bufferpix);
This fill my image correctly.
Too bad that setPixels can't take a byte array for parameter, which make the conversion uggly (I haven't look for a better way to copy my bytes array in a int array yet, probably there is one).