Related
I'm trying to have a Kotlin thread which simply reads multiple images from a single InputStream.
For testing, I have an input stream that receives the content of two small image files in a separate thread. This seems to be working correctly as if I write the content of this input stream to disk, the resulting file is identical to the concatenation of the two source image files.
The problem occurs when reading images from the input stream with ImageIO:
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStream;
import javax.imageio.ImageIO;
class ImgReader {
InputStream input;
ImgReader(InputStream input) {
this.input = input;
}
public void run() {
ImageIO.setUseCache(false);
System.out.println("read start");
int counter = 1;
try {
BufferedImage im = ImageIO.read(input);
System.out.println("read: " + counter + " " + (im != null));
if (im != null)
ImageIO.write(im, "jpg", new File("pics/out/" + (counter++) +".jpeg"));
} catch (Exception e){
System.out.println("error while reading stream");
e.printStackTrace(System.out);
}
System.out.println("read done");
}
}
This works for the first image, which is received and saved to file correctly. However, the second image is not read: ImageIO.read(input) returns null.
Is it possible to read multiple images from an InputStream? What am I doing wrong?
--- EDIT ---
I tried a variation, where only one image is decoded from the stream (this is done correctly). After this, I tried saving the rest of the stream content into a binary file, without trying to decode it as an image. This second binary file is empty, meaning that the first ImageIO.read seems to consume the whole stream.
Yes, it is possible to read multiple images from a (single) InputStream.
I believe the most obvious solution is to use a file format that already has widespread support for multiple images, like TIFF. The javax.imageio API has good support for reading and writing multi-image files, even though the ImageIO class doesn't have any convenience methods for it, like the ImageIO.read(...)/ImageIO.write(...) methods for reading/writing a single image. This means the you need to write a bit more code (code samples below).
However, if the input is created by a third-party outside of your control, using a different format may not be an option. From the comments, it is explained that your input is actually a stream of concatenated Exif JPEGs. The good news is that Java's JPEGImageReader/Writer does allow multiple JPEGs in the same stream, even though this is not a very common format.
To read multiple JPEGs from the same stream, you can use the following example (note that the code is completely generic, and will work for reading other multi-image files, like TIFF too):
File file = ...; // May also use InputStream here
List<BufferedImage> images = new ArrayList<>();
try (ImageInputStream in = ImageIO.createImageInputStream(file)) {
Iterator<ImageReader> readers = ImageIO.getImageReaders(in);
if (!readers.hasNext()) {
throw new AssertionError("No reader for file " + file);
}
ImageReader reader = readers.next();
reader.setInput(in);
// It's possible to use reader.getNumImages(true) and a for-loop here.
// However, for many formats, it is more efficient to just read until there's no more images in the stream.
try {
int i = 0;
while (true) {
images.add(reader.read(i++));
}
}
catch (IndexOutOfBoundsException expected) {
// We're done
}
reader.dispose();
}
Anything below this line is just bonus extra-information.
Here's how to write multi-image files using the ImageIO API (the code example uses TIFF, but it is quite generic, and should in theory also work for other formats, except for the compression type parameter).
File file = ...; // May also use OutputStream/InputStream here
List<BufferedImage> images = new ArrayList<>(); // Just add images...
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("TIFF");
if (!writers.hasNext()) {
throw new AssertionError("Missing plugin");
}
ImageWriter writer = writers.next();
if (!writer.canWriteSequence()) {
throw new AssertionError("Plugin doesn't support multi page file");
}
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionType("JPEG"); // The allowed compression types may vary from plugin to plugin
// The most common values for TIFF, are NONE, LZW, Deflate or Zip, or JPEG
try (ImageOutputStream out = ImageIO.createImageOutputStream(file)) {
writer.setOutput(out);
writer.prepareWriteSequence(null); // No stream metadata needed for TIFF
for (BufferedImage image : images) {
writer.writeToSequence(new IIOImage(image, null, null), param);
}
writer.endWriteSequence();
}
writer.dispose();
Note that before Java 9, you will also need a third party TIFF plugin, like JAI or my own TwelveMonkeys ImageIO, to read/write TIFF using ImageIO.
Another option, if you really don't like to write this verbose code, is to wrap the images in your own minimal container format, that includes (at least) the length of each image. Then you can write using ImageIO.write(...) and read using ImageIO.read(...), but you need to implement some simple stream logic around it. And the main argument against it, of course, is that it will be entirely proprietary.
But, if you are reading/writing asynchronously in a client/server-like setup (as I suspect, from your question), this may make perfect sense, and could be an acceptable trade-off.
Something like:
File file = new File(args[0]);
List<BufferedImage> images = new ArrayList<>();
try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024 * 1024); // Use larger buffer for large images
for (BufferedImage image : images) {
buffer.reset();
ImageIO.write(image, "JPEG", buffer); // Or PNG or any other format you like, really
out.writeInt(buffer.size());
buffer.writeTo(out);
out.flush();
}
out.writeInt(-1); // EOF marker (alternatively, catch EOFException while reading)
}
// And, reading back:
try (DataInputStream in = new DataInputStream(new FileInputStream(file))) {
int size;
while ((size = in.readInt()) != -1) {
byte[] buffer = new byte[size];
in.readFully(buffer); // May be more efficient to create a FilterInputStream that counts bytes read, with local EOF after size
images.add(ImageIO.read(new ByteArrayInputStream(buffer)));
}
}
PS: If all you want to do is to write the images you receive to disk, you should not use ImageIO for this. Instead, use plain I/O (assuming format from the previous example):
try (DataInputStream in = new DataInputStream(new FileInputStream(file))) {
int counter = 0;
int size;
while ((size = in.readInt()) != -1) {
byte[] buffer = new byte[size];
in.readFully(buffer);
try (FileOutputStream out = new FileOutputStream(new File("pics/out/" + (counter++) +".jpeg"))) {
out.write(buffer);
out.flush();
}
}
}
This is a well known "feature" of the inputstreams.
An inputstream can be read only once (ok, there is mark() and reset(), but not every implementation supports it (check markSupported() in Javadoc), and IMHO it is not so convinient to use), you should either persist your image and pass the path as an argument, or you should read it to a byte array and create a ByteArrayInputStream for every call where you are trying to read it:
// read your original stream once (e.g. with commons IO, just the sake of shortness)
byte[] imageByteArray = IOUtils.toByteArray(input);
...
// and create new input stream every time
InputStream newInput = new ByteArrayInputStream(imageByteArray);
...
// and call your reader in this way:
new ImgReader(newInput);
Update:
Scroll down to the last code snippet for an update to this answer.
It is not a satisfactory answer, but an answer to the question:
No, this is (almost certainly) not possible.
When passing an InputStream to ImageIO, it will internally be wrapped into an ImageInputStream. This stream is then passed to an ImageReader. The exact implementation will depend on the type of the image data. (This is usually determined from the "magic header", i.e. the first few bytes of the input data).
Now, the behavior of these ImageReader implementations cannot be altered or controlled sensibly. (For some of them the actual reading even takes place in native methods).
The following is an example showing the different behaviors:
First, it generates an input stream containing one JPG image and one PNG image. The output shows that the input stream is read fully before the JPG image is returned.
Then, it generates an input stream containing one PNG and one JPG image. One can see that it only reads a few bytes, until it can decode the result of the first PNG image.
_
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
public class MultipleImagesFromSingleStream
{
public static void main(String[] args) throws IOException
{
readJpgAndPng();
readPngAndJpg();
}
private static void readJpgAndPng() throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(createDummyImage("Image 0", 50), "jpg", baos);
ImageIO.write(createDummyImage("Image 1", 60), "png", baos);
byte data[] = baos.toByteArray();
InputStream inputStream = createSlowInputStream(data);
BufferedImage image0 = ImageIO.read(inputStream);
System.out.println("Read " + image0);
BufferedImage image1 = ImageIO.read(inputStream);
System.out.println("Read " + image1);
}
private static void readPngAndJpg() throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(createDummyImage("Image 0", 50), "png", baos);
ImageIO.write(createDummyImage("Image 1", 60), "jpg", baos);
byte data[] = baos.toByteArray();
InputStream inputStream = createSlowInputStream(data);
BufferedImage image0 = ImageIO.read(inputStream);
System.out.println("Read " + image0);
BufferedImage image1 = ImageIO.read(inputStream);
System.out.println("Read " + image1);
}
private static InputStream createSlowInputStream(byte data[])
{
ByteArrayInputStream bais = new ByteArrayInputStream(data);
return new InputStream()
{
private long counter = 0;
#Override
public int read() throws IOException
{
counter++;
if (counter % 100 == 0)
{
System.out.println(
"Read " + counter + " of " + data.length + " bytes");
try
{
Thread.sleep(50);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
return bais.read();
}
};
}
private static BufferedImage createDummyImage(String text, int h)
{
int w = 100;
BufferedImage image =
new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
g.setColor(Color.BLACK);
g.fillRect(0, 0, w, h);
g.setColor(Color.WHITE);
g.drawString(text, 20, 20);
g.dispose();
return image;
}
}
The output is as follows:
Read 100 of 1519 bytes
Read 200 of 1519 bytes
Read 300 of 1519 bytes
Read 400 of 1519 bytes
Read 500 of 1519 bytes
Read 600 of 1519 bytes
Read 700 of 1519 bytes
Read 800 of 1519 bytes
Read 900 of 1519 bytes
Read 1000 of 1519 bytes
Read 1100 of 1519 bytes
Read 1200 of 1519 bytes
Read 1300 of 1519 bytes
Read 1400 of 1519 bytes
Read 1500 of 1519 bytes
Read BufferedImage#3eb07fd3: type = 0 DirectColorModel: rmask=ff000000 gmask=ff0000 bmask=ff00 amask=ff IntegerInterleavedRaster: width = 100 height = 50 #Bands = 4 xOff = 0 yOff = 0 dataOffset[0] 0
Read null
Read 100 of 1499 bytes
Read 200 of 1499 bytes
Read BufferedImage#42110406: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace#531d72ca transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 100 height = 50 #numDataElements 4 dataOff[0] = 3
Read null
Note that although it does not read the full stream in the second case, this still does not necessarily mean that the input stream then is located at the "beginning of the JPG data". It only means that it does not read the full stream!
I also tried to dive deeper into this. Iff one could be sure that the images are always only PNG images, one could try to manually create a PNGImageReader instance and hook into its reading process, to check when it actually finished the first image. But again, the input stream is wrapped internally into several other (buffered- and deflating) input streams, and there is no way to sensibly detect whether a certain set of bytes already has been "used" for an image.
So I think that the only sensible solution here is to close the stream after the image has been read, and open a new stream for the next image.
A workaround that has been discussed in the comments is to add length information to the stream. This means that the producer of the image data first writes an int into the stream, describing the length of the image data. Then it writes the byte[length] data with the actual image data.
The receiver can then use this information to load individual images.
This is implemented here, as an example:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class MultipleImagesFromSingleStreamWorkaround
{
public static void main(String[] args) throws IOException
{
workaround();
}
private static void workaround() throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
write(createDummyImage("Image 0", 50), "jpg", baos);
write(createDummyImage("Image 1", 60), "png", baos);
write(createDummyImage("Image 2", 70), "gif", baos);
byte data[] = baos.toByteArray();
InputStream inputStream = createSlowInputStream(data);
BufferedImage image0 = read(inputStream);
System.out.println("Read " + image0);
BufferedImage image1 = read(inputStream);
System.out.println("Read " + image1);
BufferedImage image2 = read(inputStream);
System.out.println("Read " + image2);
showImages(image0, image1, image2);
}
private static void write(BufferedImage bufferedImage,
String formatName, OutputStream outputStream) throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, formatName, baos);
byte data[] = baos.toByteArray();
DataOutputStream dos = new DataOutputStream(outputStream);
dos.writeInt(data.length);
dos.write(data);
dos.flush();
}
private static BufferedImage read(
InputStream inputStream) throws IOException
{
DataInputStream dis = new DataInputStream(inputStream);
int length = dis.readInt();
byte data[] = new byte[length];
dis.read(data);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
return ImageIO.read(bais);
}
private static InputStream createSlowInputStream(byte data[])
{
ByteArrayInputStream bais = new ByteArrayInputStream(data);
return new InputStream()
{
private long counter = 0;
#Override
public int read() throws IOException
{
counter++;
if (counter % 100 == 0)
{
System.out.println(
"Read " + counter + " of " + data.length + " bytes");
try
{
Thread.sleep(50);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
return bais.read();
}
};
}
private static BufferedImage createDummyImage(String text, int h)
{
int w = 100;
BufferedImage image =
new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
g.setColor(Color.BLACK);
g.fillRect(0, 0, w, h);
g.setColor(Color.WHITE);
g.drawString(text, 20, 20);
g.dispose();
return image;
}
private static void showImages(BufferedImage ... images)
{
SwingUtilities.invokeLater(() ->
{
JFrame f = new JFrame();
f.getContentPane().setLayout(new GridLayout(1,0));
for (BufferedImage image : images)
{
f.getContentPane().add(new JLabel(new ImageIcon(image)));
}
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
}
Update
This is based on the answer from haraldK (upvote his answer, not this one!)
An example implementation showing the approach that haraldK proposed. It manages to read a sequence of images, although there are some limitations:
It seems to have to read "more" bytes than strictly necessary before it delivers the first image.
It cannot load different types of images (i.e. it cannot read a sequence of mixed PNG and JPG images)
Specifically, it seemed to work only for JPG images for me. For PNG or GIF, only the first image was read (at least for me...)
However, posting it here for others to easily test it:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class MultipleImagesFromSingleStreamWorking
{
public static void main(String[] args) throws IOException
{
readExample();
}
private static void readExample() throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(createDummyImage("Image 0", 50), "jpg", baos);
//ImageIO.write(createDummyImage("Image 1", 60), "png", baos);
ImageIO.write(createDummyImage("Image 2", 70), "jpg", baos);
ImageIO.write(createDummyImage("Image 3", 80), "jpg", baos);
ImageIO.write(createDummyImage("Image 4", 90), "jpg", baos);
ImageIO.write(createDummyImage("Image 5", 100), "jpg", baos);
ImageIO.write(createDummyImage("Image 6", 110), "jpg", baos);
ImageIO.write(createDummyImage("Image 7", 120), "jpg", baos);
byte data[] = baos.toByteArray();
InputStream inputStream = createSlowInputStream(data);
List<BufferedImage> images = readImages(inputStream);
showImages(images);
}
private static List<BufferedImage> readImages(InputStream inputStream)
throws IOException
{
// From https://stackoverflow.com/a/53501316/3182664
List<BufferedImage> images = new ArrayList<BufferedImage>();
try (ImageInputStream in = ImageIO.createImageInputStream(inputStream))
{
Iterator<ImageReader> readers = ImageIO.getImageReaders(in);
if (!readers.hasNext())
{
throw new AssertionError("No reader for file " + inputStream);
}
ImageReader reader = readers.next();
reader.setInput(in);
// It's possible to use reader.getNumImages(true) and a for-loop
// here.
// However, for many formats, it is more efficient to just read
// until there's no more images in the stream.
try
{
int i = 0;
while (true)
{
BufferedImage image = reader.read(i++);
System.out.println("Read " + image);
images.add(image);
}
}
catch (IndexOutOfBoundsException expected)
{
// We're done
}
reader.dispose();
}
return images;
}
private static InputStream createSlowInputStream(byte data[])
{
ByteArrayInputStream bais = new ByteArrayInputStream(data);
return new InputStream()
{
private long counter = 0;
#Override
public int read() throws IOException
{
counter++;
if (counter % 100 == 0)
{
System.out.println(
"Read " + counter + " of " + data.length + " bytes");
try
{
Thread.sleep(50);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
return bais.read();
}
};
}
private static BufferedImage createDummyImage(String text, int h)
{
int w = 100;
BufferedImage image =
new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
g.setColor(Color.BLACK);
g.fillRect(0, 0, w, h);
g.setColor(Color.WHITE);
g.drawString(text, 20, 20);
g.dispose();
return image;
}
private static void showImages(List<BufferedImage> images)
{
SwingUtilities.invokeLater(() ->
{
JFrame f = new JFrame();
f.getContentPane().setLayout(new GridLayout(1,0));
for (BufferedImage image : images)
{
f.getContentPane().add(new JLabel(new ImageIcon(image)));
}
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
}
public void actionPerformed(java.awt.event.ActionEvent evt) {
Connection cn = null;
Object source = evt.getSource();
JFileChooser filechooser= new JFileChooser();
filechooser.setDialogTitle("Choose Your File");
filechooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
int returnval=filechooser.showOpenDialog(this);
if(returnval==JFileChooser.APPROVE_OPTION)
{
File file = filechooser.getSelectedFile();
BufferedImage bi;
try
{
bi=ImageIO.read(file);
lbl_movieCover.setIcon(new ImageIcon(bi));
}
catch(IOException e)
{
}
//this.pack();
}
Above is my code for choosing the image and displaying the image to JLabel. My problem is that, I don't know how to convert it to byte[] so I could save it to my database. By the way, I'm using MySQL for my database. If you guys know how to do it, please let me know.
Use ImageIO.write to write the image through a ByteArrayOutputStream, for example
ByteArrayOutputStream baos = null;
try {
baos = new ByteArrayOutputStream();
ImageIO.write(bi, "png", baos);
} finally {
try {
baos.close();
} catch (Exception e) {
}
}
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
You can then use the resulting byte[] array or ByteArrayInputStream and pass this to the setBlob method of a PreparedStatement
You could use a ByteArrayOutputStream and ImageIO to write an image to a byte array, like this:
static byte[] imageToByteArray(BufferedImage image) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try {
ImageIO.write(image, "png", stream);
} catch(IOException e) {
// This *shouldn't* happen with a ByteArrayOutputStream, but if it
// somehow does happen, then we don't want to just ignore it
throw new RuntimeException(e);
}
return stream.toByteArray();
// ByteArrayOutputStreams don't need to be closed (the documentation says so)
}
You can use this method-
/**
* #param userSpaceImage
* #return byte array of supplied image
*/
public byte[] getByteData(BufferedImage userSpaceImage) {
WritableRaster raster = userSpaceImage.getRaster();
DataBufferByte buffer = (DataBufferByte) raster.getDataBuffer();
return buffer.getData();
}
Use it like-
//file from filechooser
BufferedImage originalImage = ImageIO.read(file);
byte image[] = getByteData(originalImage);
Note that if image type is that of int e.g. BufferedImage.TYPE_INT_RGB then
you will get cast exception. Following method can be used to convert to suitable type-
/**
* this method convert supplied image to suitable type
* it is needed because we need bytes of array so TYPE_INT images must be
* converted to BYTE_BGR or so
* #param originalImage loaded from file-chooser
* #return
*/
public BufferedImage convertImage(BufferedImage originalImage) {
int newImageType = originalImage.getType();
/**
* Converting int to byte since byte array is needed later to modify
* the image
*/
if (newImageType == BufferedImage.TYPE_INT_RGB
|| newImageType == BufferedImage.TYPE_INT_BGR) {
newImageType = BufferedImage.TYPE_3BYTE_BGR;
} else if (newImageType == BufferedImage.TYPE_INT_ARGB) {
newImageType = BufferedImage.TYPE_4BYTE_ABGR;
} else if (newImageType == BufferedImage.TYPE_INT_ARGB_PRE) {
newImageType = BufferedImage.TYPE_4BYTE_ABGR_PRE;
}
BufferedImage newImage = new BufferedImage(originalImage.getWidth(),
originalImage.getHeight(), newImageType);
Graphics g = newImage.getGraphics();
g.drawImage(originalImage, 0, 0, null);
g.dispose();
return newImage;
}
While both #MadProgrammer's and #immibis' answers are technically correct and answers your question, you don't really want to copy an image file by decoding it, and re-encoding it. This is because it's slower, but more importantly, you will lose quality for certain image formats (most notably JPEG) and any metadata associated with the image will be lost (this last part could of course be intentional, but there are better ways to do this without ruining the image quality).
So, instead, do as #immibis seems to hint at in his comment, just open a FileInputStream and read the bytes directly from the file, and into the database. You should be able to open an OutputStream to the blob in your database as well, so you can save some memory (a good thing, if your files are large) by not reading the entire file contents into a byte array, before writing.
Something like:
File file = filechooser.getSelectedFile();
// ... show image in label as before (but please, handle the IOException)
InputStream input = new FileInputStream(file);
try {
Blob blob = ...; // Get from JDBC connection
OutputStream output = blob.setBinaryStream(0);
try {
FileUtils.copy(input, output);
}
finally {
output.close();
}
}
finally {
input.close();
}
FileUtils.copy can be implemented as:
public void copy(final InputStream in, final OutputStream out) {
byte[] buffer = new byte[1024];
int count;
while ((count = in.read(buffer)) != -1) {
out.write(buffer, 0, count);
}
// Flush out stream, to write any remaining buffered data
out.flush();
}
I am trying to crop/resize user profile image using jquery plugin namely crop.js which sends user image as base64 via ajax to my controller as
$.ajax({
type: "post",
dataType: "json",
url: "${g.createLink(controller: 'personalDetail', action:'uploadUserImage')}",
data: { avatar: canvas.toDataURL() }
});
but I unable to decode this base64
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwCAYAAAA+VemSAAAgAEl...=='
string as Image,Can you guys guide me how can I save my base64 string as image on my server?.
In the server, do something like this:
Suppose
String data = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwCAYAAAA+VemSAAAgAEl...=='
Then:
String base64Image = data.split(",")[1];
byte[] imageBytes = javax.xml.bind.DatatypeConverter.parseBase64Binary(base64Image);
Then you can do whatever you like with the bytes like:
BufferedImage img = ImageIO.read(new ByteArrayInputStream(imageBytes));
This assumes a few things, that you know what the output file name will be and that your data comes as a string. I'm sure you can modify the following to meet your needs:
// Needed Imports
import java.io.ByteArrayInputStream;
import sun.misc.BASE64Decoder;
def sourceData = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwCAYAAAA+VemSAAAgAEl...==';
// tokenize the data
def parts = sourceData.tokenize(",");
def imageString = parts[1];
// create a buffered image
BufferedImage image = null;
byte[] imageByte;
BASE64Decoder decoder = new BASE64Decoder();
imageByte = decoder.decodeBuffer(imageString);
ByteArrayInputStream bis = new ByteArrayInputStream(imageByte);
image = ImageIO.read(bis);
bis.close();
// write the image to a file
File outputfile = new File("image.png");
ImageIO.write(image, "png", outputfile);
Please note, this is just an example of what parts are involved. I haven't optimized this code at all and it's written off the top of my head.
ImageIO.write() will compress the image by default - the compressed image has a smaller size but looks strange sometimes. I use BufferedOutputStream to save the byte array data - this will keep the original image size.
Here is the code:
import javax.xml.bind.DatatypeConverter;
import java.io.*;
public class ImageTest {
public static void main(String[] args) {
String base64String = "data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAHkAAAB5C...";
String[] strings = base64String.split(",");
String extension;
switch (strings[0]) {//check image's extension
case "data:image/jpeg;base64":
extension = "jpeg";
break;
case "data:image/png;base64":
extension = "png";
break;
default://should write cases for more images types
extension = "jpg";
break;
}
//convert base64 string to binary data
byte[] data = DatatypeConverter.parseBase64Binary(strings[1]);
String path = "C:\\Users\\Ene\\Desktop\\test_image." + extension;
File file = new File(path);
try (OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file))) {
outputStream.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Simplicity is:
import java.util.Base64;
To decode:
byte[] image = Base64.getDecoder().decode(base64string);
To encode:
String text = Base64.getEncoder().encodeToString(imageData);
Server side encoding files/Images to base64String ready for client side consumption
public Optional<String> InputStreamToBase64(Optional<InputStream> inputStream) throws IOException{
if (inputStream.isPresent()) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
FileCopyUtils.copy(inputStream.get(), output);
//TODO retrieve content type from file, & replace png below with it
return Optional.ofNullable("data:image/png;base64," + DatatypeConverter.printBase64Binary(output.toByteArray()));
}
return Optional.empty();
}
Server side base64 Image/File decoder
public Optional<InputStream> Base64InputStream(Optional<String> base64String)throws IOException {
if (base64String.isPresent()) {
return Optional.ofNullable(new ByteArrayInputStream(DatatypeConverter.parseBase64Binary(base64String.get())));
}
return Optional.empty();
}
public Optional<String> InputStreamToBase64(Optional<InputStream> inputStream) throws IOException{
if (inputStream.isPresent()) {
ByteArrayOutputStream outpString base64Image = data.split(",")[1];
byte[] imageBytes = javax.xml.bind.DatatypeConverter.parseBase64Binary(base64Image);
Then you can do whatever you like with the bytes like:
BufferedImage img = ImageIO.read(new ByteArrayInputStream(imageBytes));ut = new ByteArrayOutputStream();
FileCopyUtils.copy(inputStream.get(), output);
//TODO retrieve content type from file, & replace png below with it
return Optional.ofNullable("data:image/png;base64," + DatatypeConverter.printBase64Binary(output.toByteArray()));
}
return Optional.empty();
Hi This is my solution
Javascript code
var base64before = document.querySelector('img').src;
var base64 = base64before.replace(/^data:image\/(png|jpg);base64,/, "");
var httpPost = new XMLHttpRequest();
var path = "your url";
var data = JSON.stringify(base64);
httpPost.open("POST", path, false);
// Set the content type of the request to json since that's what's being sent
httpPost.setRequestHeader('Content-Type', 'application/json');
httpPost.send(data);
This is my Java code.
public void saveImage(InputStream imageStream){
InputStream inStream = imageStream;
try {
String dataString = convertStreamToString(inStream);
byte[] imageBytes = javax.xml.bind.DatatypeConverter.parseBase64Binary(dataString);
BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageBytes));
// write the image to a file
File outputfile = new File("/Users/paul/Desktop/testkey/myImage.png");
ImageIO.write(image, "png", outputfile);
}catch(Exception e) {
System.out.println(e.getStackTrace());
}
}
static String convertStreamToString(java.io.InputStream is) {
java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
This is Ayano's excellent answer, but in Clojure:
(:import (java.awt.image BufferedImage)
(javax.xml.bind DatatypeConverter)
(java.io File BufferedOutputStream FileOutputStream))
(defn write-svg-encountered-image [svg-img-data base-filename]
(let [[meta img-data] (str/split svg-img-data #",")
extension (case meta
"data:image/jpeg;base64" ".jpeg"
"data:image/png;base64" ".png"
(throw (Error. (format "Unrecognised image metadata in SVG:" meta))))
path (str base-filename extension)
file (File. path)
data-bytes (DatatypeConverter/parseBase64Binary img-data)
os (BufferedOutputStream. (FileOutputStream. file))]
(.write os data-bytes)))
To Convert all file types
String[] strings = expense.getAttchBase64().split(",");
String extension;
switch (strings[0]) {//check image's extension
case "data:image/jpeg;base64":
extension = "jpeg";
break;
case "data:image/png;base64":
extension = "png";
break;
case "data:application/pdf;base64":
extension = "pdf";
break;
case "data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64":
extension = "docx";
break;
default://should write cases for more images types
extension = "jpg";
break;
}
//convert base64 string to binary data
byte[] data = DatatypeConverter.parseBase64Binary(strings[1]);
String fileName = UUID.randomUUID().toString();
String path = "C:\\floridatrading\\" + fileName + "." + extension;
File file = new File(path);
try (OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file))) {
outputStream.write(data);
} catch (IOException e) {
e.printStackTrace();
}
if you need to add any new type just add It in switch .
I want to read an image from a url and convert it into binary data. Please help me..
byte[] data = null;
ByteArrayOutputStream bas = null;
try {
URL u = new URL(
"http://www.eso.org/public/archives/images/screen/eso0844a.jpg");
HttpURLConnection con1 = (HttpURLConnection) u.openConnection();
con1.setAllowUserInteraction(true);
con1.setRequestMethod("GET");
con1.connect();
InputStream is = con1.getInputStream();
BufferedImage imgToServe = null;
if (is != null) {
imgToServe = ImageIO.read(is);
}
bas = new ByteArrayOutputStream();
ImageIO.write(imgToServe, "jpg", bas);
File f = new File("C:\\img.jpg");
ImageIO.write(imgToServe, "jpg", f);
data = bas.toByteArray();
String str = "";
for (int i = 0; i < data.length; i++) {
str = str + toBinary(data[i]);
}
System.out.println(str);
} catch (HTTPException he) {
} catch (IOException ioe) {
}
}
private static String toBinary(byte b) {
StringBuilder sb = new StringBuilder("00000000");
for (int bit = 0; bit < 8; bit++) {
if (((b >> bit) & 1) > 0) {
sb.setCharAt(7 - bit, '1');
}
}
return (sb.toString());
}
If you're reading the image from a URL, it will already be in a binary format. Just download the data and ignore the fact that it's an image. The code which is involved in download it won't care, after all. Assuming you want to write it to a file or something similar, just open the URLConnection and open the FileOutputStream, and repeatedly read from the input stream from the web, writing the data you've read to the output stream.
If that's not what you were after, please clarify the question.
EDIT: If you really want to get the data as individual bits (which seems somewhat odd to me) you should separate the problem in two:
Downloading the data (see above; if you don't need it on disk, consider writing to a ByteArrayOutputStream)
Converting arbitrary binary data (a byte array or an input stream) into 0s and 1s
How you tackle the latter task will depend on what you actually want to do with the bits. What's the real aim here?
You can use the standard ImageIO for this. The read method takes a URL and retrieves it to an Image. Then you can use the write method to write it to a File or like in this case a ByteArrayOutputStream which outputs the image to a in-memory buffer.
public static void main(String[] args) throws Exception {
// read "any" type of image (in this case a png file)
BufferedImage image = ImageIO.read(new URL("http://upload.wikimedia.org/wikipedia/en/2/24/Lenna.png"));
// write it to byte array in-memory (jpg format)
ByteArrayOutputStream b = new ByteArrayOutputStream();
ImageIO.write(image, "jpg", b);
// do whatever with the array...
byte[] jpgByteArray = b.toByteArray();
// convert it to a String with 0s and 1s
StringBuilder sb = new StringBuilder();
for (byte by : jpgByteArray)
sb.append(Integer.toBinaryString(by & 0xFF));
System.out.println(sb.toString());
}
Load the image from path/url into BufferedImage
public static Raster loadImageRaster(String file_path) throws IOException
{
File input = new File(file_path);
BufferedImage buf_image = ImageIO.read(input);
buf_image = binarizeImage(buf_image);
return buf_image.getData(); //return raster
}
Make a Binary Type BufferedImage from the original BufferedImage
public static BufferedImage binarizeImage(BufferedImage img_param)
{
BufferedImage image = new BufferedImage(
img_param.getWidth(),
img_param.getHeight(),
BufferedImage.TYPE_BYTE_BINARY
);
Graphics g = image.getGraphics();
g.drawImage(img_param, 0, 0, null);
g.dispose();
return image;
}
Convert the BufferedImage to Raster so that you can manipulate it pixel by pixel
imageRaster.getSample(x, y, 0)
Raster.getSample(x,y, channel) will return 0s or 1s.
channel = 0 for TYPE_BYTE_BINARY images
Okay, so I'm going to take the off chance that someone here has used zxing before. I'm developing a Java application, and one of the things it needs to do is encode a byte array of data into a QR Code and then decode it at a later time.
Here's an example of what my encoder looks like:
byte[] b = {0x48, 0x45, 0x4C, 0x4C, 0x4F};
//convert the byte array into a UTF-8 string
String data;
try {
data = new String(b, "UTF8");
}
catch (UnsupportedEncodingException e) {
//the program shouldn't be able to get here
return;
}
//get a byte matrix for the data
ByteMatrix matrix;
com.google.zxing.Writer writer = new QRCodeWriter();
try {
matrix = writer.encode(data, com.google.zxing.BarcodeFormat.QR_CODE, width, height);
}
catch (com.google.zxing.WriterException e) {
//exit the method
return;
}
//generate an image from the byte matrix
int width = matrix.getWidth();
int height = matrix.getHeight();
byte[][] array = matrix.getArray();
//create buffered image to draw to
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
//iterate through the matrix and draw the pixels to the image
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int grayValue = array[y][x] & 0xff;
image.setRGB(x, y, (grayValue == 0 ? 0 : 0xFFFFFF));
}
}
//write the image to the output stream
ImageIO.write(image, "png", outputStream);
The beginning byte array in this code is just used to test it. The actual byte data will be varied.
Here's what my decoder looks like:
//get the data from the input stream
BufferedImage image = ImageIO.read(inputStream);
//convert the image to a binary bitmap source
LuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
//decode the barcode
QRCodeReader reader = new QRCodeReader();
Result result;
try {
result = reader.decode(bitmap, hints);
} catch (ReaderException e) {
//the data is improperly formatted
throw new MCCDatabaseMismatchException();
}
byte[] b = result.getRawBytes();
System.out.println(ByteHelper.convertUnsignedBytesToHexString(result.getText().getBytes("UTF8")));
System.out.println(ByteHelper.convertUnsignedBytesToHexString(b));
convertUnsignedBytesToHexString(byte) is a method which converts an array of bytes in a string of hexadecimal characters.
When I try to run these two blocks of code together, this is the output:
48454c4c4f
202b0b78cc00ec11ec11ec11ec11ec11ec11ec
Clearly the text is being encoded, but the actual bytes of data are completely off. Any help would be appreciated here.
So, for future reference for anybody who doesn't want to spend two days searching the internet to figure this out, when you encode byte arrays into QR Codes, you have to use the ISO-8859-1character set, not UTF-8.
this is my working example Java code to encode QR code using ZXing with UTF-8 encoding, please note: you will need to change the path and utf8 data to your path and language characters
package com.mypackage.qr;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.Hashtable;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.*;
public class CreateQR {
public static void main(String[] args)
{
Charset charset = Charset.forName("UTF-8");
CharsetEncoder encoder = charset.newEncoder();
byte[] b = null;
try {
// Convert a string to UTF-8 bytes in a ByteBuffer
ByteBuffer bbuf = encoder.encode(CharBuffer.wrap("utf 8 characters - i used hebrew, but you should write some of your own language characters"));
b = bbuf.array();
} catch (CharacterCodingException e) {
System.out.println(e.getMessage());
}
String data;
try {
data = new String(b, "UTF-8");
// get a byte matrix for the data
BitMatrix matrix = null;
int h = 100;
int w = 100;
com.google.zxing.Writer writer = new MultiFormatWriter();
try {
Hashtable<EncodeHintType, String> hints = new Hashtable<EncodeHintType, String>(2);
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
matrix = writer.encode(data,
com.google.zxing.BarcodeFormat.QR_CODE, w, h, hints);
} catch (com.google.zxing.WriterException e) {
System.out.println(e.getMessage());
}
// change this path to match yours (this is my mac home folder, you can use: c:\\qr_png.png if you are on windows)
String filePath = "/Users/shaybc/Desktop/OutlookQR/qr_png.png";
File file = new File(filePath);
try {
MatrixToImageWriter.writeToFile(matrix, "PNG", file);
System.out.println("printing to " + file.getAbsolutePath());
} catch (IOException e) {
System.out.println(e.getMessage());
}
} catch (UnsupportedEncodingException e) {
System.out.println(e.getMessage());
}
}
}
For what it's worth, my groovy spike seems to work with both UTF-8 and ISO-8859-1 character encodings. Not sure what will happen when a non zxing decoder tries to decode the UTF-8 encoded image though... probably varies depending on the device.
// ------------------------------------------------------------------------------------
// Requires: groovy-1.7.6, jdk1.6.0_03, ./lib with zxing core-1.7.jar, javase-1.7.jar
// Javadocs: http://zxing.org/w/docs/javadoc/overview-summary.html
// Run with: groovy -cp "./lib/*" zxing.groovy
// ------------------------------------------------------------------------------------
import com.google.zxing.*
import com.google.zxing.common.*
import com.google.zxing.client.j2se.*
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
def class zxing {
def static main(def args) {
def filename = "./qrcode.png"
def data = "This is a test to see if I can encode and decode this data..."
def charset = "UTF-8" //"ISO-8859-1"
def hints = new Hashtable<EncodeHintType, String>([(EncodeHintType.CHARACTER_SET): charset])
writeQrCode(filename, data, charset, hints, 100, 100)
assert data == readQrCode(filename, charset, hints)
}
def static writeQrCode(def filename, def data, def charset, def hints, def width, def height) {
BitMatrix matrix = new MultiFormatWriter().encode(new String(data.getBytes(charset), charset), BarcodeFormat.QR_CODE, width, height, hints)
MatrixToImageWriter.writeToFile(matrix, filename.substring(filename.lastIndexOf('.')+1), new File(filename))
}
def static readQrCode(def filename, def charset, def hints) {
BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(new BufferedImageLuminanceSource(ImageIO.read(new FileInputStream(filename)))))
Result result = new MultiFormatReader().decode(binaryBitmap, hints)
result.getText()
}
}
If you really need to encode UTF-8, you can try prepending the unicode byte order mark. I have no idea how widespread the support for this method is, but ZXing at least appears to support it:
http://code.google.com/p/zxing/issues/detail?id=103
I've been reading up on QR Mode recently, and I think I've seen the same practice mentioned elsewhere, but I've not the foggiest where.
I tried using ISO-8859-1 as said in the first answer. All went ok on encoding, but when I tried to get the byte[] using result string on decoding, all negative bytes became the character 63 (question mark). The following code does not work:
// Encoding works great
byte[] contents = new byte[]{-1};
QRCodeWriter codeWriter = new QRCodeWriter();
BitMatrix bitMatrix = codeWriter.encode(new String(contents, Charset.forName("ISO-8859-1")), BarcodeFormat.QR_CODE, w, h);
// Decodes like this fails
LuminanceSource ls = new BufferedImageLuminanceSource(encodedBufferedImage);
Result result = new QRCodeReader().decode(new BinaryBitmap( new HybridBinarizer(ls)));
byte[] resultBytes = result.getText().getBytes(Charset.forName("ISO-8859-1")); // a byte[] with byte 63 is given
return resultBytes;
It looks so strange because the API in a very old version (don't know exactly) had a method thar works well:
Vector byteSegments = result.getByteSegments();
So I tried to search why this method was removed and realized that there is a way to get ByteSegments, through metadata. So my decode method looks like:
// Decodes like this works perfectly
LuminanceSource ls = new BufferedImageLuminanceSource(encodedBufferedImage);
Result result = new QRCodeReader().decode(new BinaryBitmap( new HybridBinarizer(ls)));
Vector byteSegments = (Vector) result.getResultMetadata().get(ResultMetadataType.BYTE_SEGMENTS);
int i = 0;
int tam = 0;
for (Object o : byteSegments) {
byte[] bs = (byte[])o;
tam += bs.length;
}
byte[] resultBytes = new byte[tam];
i = 0;
for (Object o : byteSegments) {
byte[] bs = (byte[])o;
for (byte b : bs) {
resultBytes[i++] = b;
}
}
return resultBytes;
Maybe worth looking at QRGen, which is built on top of ZXing and supports UTF-8 with this kind of syntax:
// if using special characters don't forget to supply the encoding
VCard johnSpecial = new VCard("Jöhn Dɵe")
.setAdress("ëåäöƞ Sträät 1, 1234 Döestüwn");
QRCode.from(johnSpecial).withCharset("UTF-8").file();