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.
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 have an grayscale image with dimension 256*256.I am trying to downscale it to 128*128.
I am taking an average of two pixel and writing it to the ouput file.
class Start {
public static void main (String [] args) throws IOException {
File input= new File("E:\\input.raw");
File output= new File("E:\\output.raw");
new Start().resizeImage(input,output,2);
}
public void resizeImage(File input, File output, int downScaleFactor) throws IOException {
byte[] fileContent= Files.readAllBytes(input.toPath());
FileOutputStream stream= new FileOutputStream(output);
int i=0;
int j=1;
int result=0;
for(;i<fileContent.length;i++)
{
if(j>1){
// skip the records.
j--;
continue;
}
else {
result = fileContent[i];
for (; j < downScaleFactor; j++) {
result = ((result + fileContent[i + j]) / 2);
}
j++;
stream.write( fileContent[i]);
}
}
stream.close();
}
}
Above code run successfully , I can see the size of output file size is decreased but when I try to convert
output file (raw file) to jpg online (https://www.iloveimg.com/convert-to-jpg/raw-to-jpg) it is giving me an error saying that file is corrupt.
I have converted input file from same online tool it is working perfectly. Something is wrong with my code which is creating corrupt file.
How can I correct it ?
P.S I can not use any library which directly downscale an image .
Your code is not handling image resizing.
See how-to-resize-images-in-java.
Which, i am copying a simple version here:
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class ImageResizer {
public static void resize(String inputImagePath,
String outputImagePath, int scaledWidth, int scaledHeight)
throws IOException {
// reads input image
File inputFile = new File(inputImagePath);
BufferedImage inputImage = ImageIO.read(inputFile);
// creates output image
BufferedImage outputImage = new BufferedImage(scaledWidth,
scaledHeight, inputImage.getType());
// scales the input image to the output image
Graphics2D g2d = outputImage.createGraphics();
g2d.drawImage(inputImage, 0, 0, scaledWidth, scaledHeight, null);
g2d.dispose();
// extracts extension of output file
String formatName = outputImagePath.substring(outputImagePath
.lastIndexOf(".") + 1);
// writes to output file
ImageIO.write(outputImage, formatName, new File(outputImagePath));
}
public static void resize(String inputImagePath,
String outputImagePath, double percent) throws IOException {
File inputFile = new File(inputImagePath);
BufferedImage inputImage = ImageIO.read(inputFile);
int scaledWidth = (int) (inputImage.getWidth() * percent);
int scaledHeight = (int) (inputImage.getHeight() * percent);
resize(inputImagePath, outputImagePath, scaledWidth, scaledHeight);
}
public static void main(String[] args) {
String inputImagePath = "resources/snoopy.jpg";
String outputImagePath1 = "target/Puppy_Fixed.jpg";
String outputImagePath2 = "target/Puppy_Smaller.jpg";
String outputImagePath3 = "target/Puppy_Bigger.jpg";
try {
// resize to a fixed width (not proportional)
int scaledWidth = 1024;
int scaledHeight = 768;
ImageResizer.resize(inputImagePath, outputImagePath1, scaledWidth, scaledHeight);
// resize smaller by 50%
double percent = 0.5;
ImageResizer.resize(inputImagePath, outputImagePath2, percent);
// resize bigger by 50%
percent = 1.5;
ImageResizer.resize(inputImagePath, outputImagePath3, percent);
} catch (IOException ex) {
System.out.println("Error resizing the image.");
ex.printStackTrace();
}
}
}
This is a fairly abstract question, so it doesn't contain an SSCCE. Basically, I wish to send a graph that I am constructing as part of a JSON String. However, to do this, I need to be able to send this component (the graph) in a format that makes sense. I was wondering if there is any such format?
Sorry for the abstractness, I just have been completely stumped.
Cheers,
Kesh
To capture an image of a component, create an image, get a graphics context for the image, then ask the component to paint itself using that graphics context:
Component comp = ...;
BufferedImage image = new BufferedImage(comp.getWidth(), comp.getHeight(),
BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
comp.paint(g);
g.dispose();
(import java.awt.image.BufferedImage if not already.)
To pack this image for transport, we can compress it in PNG format and create a Base64 encoding of the binary data:
StringBuilder sb = new StringBuilder();
try (OutputStream out = new Base64Encoder(sb)) {
javax.imageio.ImageIO.write(image, "png", out);
} catch (IOException e) {
throw new RuntimeException(e);
}
String imageData = sb.toString();
My implementation of a Base64 encoder since Java doesn't have one:
import java.io.*;
public class Base64Encoder extends OutputStream {
private Appendable out;
private int p = 0, tmp = 0;
private static final char[] charMap =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
.toCharArray();
public Base64Encoder(Appendable out) {
this.out = java.util.Objects.requireNonNull(out);
}
#Override
public void write(int b) throws IOException {
b &= 0xFF;
if (p == 0) {
out.append(charMap[b >> 2]);
tmp = (b & 0x3) << 4;
p = 1;
} else if (p == 1) {
out.append(charMap[tmp | (b >> 4)]);
tmp = (b & 0xF) << 2;
p = 2;
} else {
out.append(charMap[tmp | (b >> 6)]);
out.append(charMap[b & 0x3F]);
p = 0;
}
}
#Override
public void close() throws IOException {
if (p != 0) {
out.append(charMap[tmp]);
if (p == 1) out.append('=').append('=');
if (p == 2) out.append('=');
}
out = null;
}
}
You'll end up with a long string starting with "iVBOR" (the Base64-encoded form of "PNG"), which can be easily packed in a JSON object.
If you prepend the string with "data:image/png;base64," it becomes a valid data URI that web browsers can display directly (you can link to it or use it as an <img> tag's src). I'm not sure if that's a viable way to display it on an iPad but I'm sure it's possible to figure something out.
I am using the JpegImagesToMovie.java to convert images to a .mov file. I was wondering if there was anyway I could edit this to work with .png files as the quality of the video is not very good and changing it would improve it.
ImageIO.write(img, "png", new File("C:\\Users\\user\\Desktop\\tmp\\" + System.currentTimeMillis() + ".png"));
ImageIO.write(img, "jpeg", new File("C:\\Users\\user\\Desktop\\tmp\\" + System.currentTimeMillis() + ".png"));
ImageIO.write(img, "png", new File("C:\\Users\\user\\Desktop\\tmp\\" + System.currentTimeMillis() + ".jpeg"));
All three of those would produce a video (no errors through the program) but the video wouldn't play the images just open and finish.
I also tried editing the JpegImagesToMovie.java
if (!filePath.toLowerCase().endsWith(".png") && !filePath.toLowerCase().endsWith(".png")) {
continue;
But that didn't work, and I can't find anywhere else to edit. How can I get this to work with .png images?
Latest Update:
Here is my updated JpegImagesToMovies class
package maple;
/*
* #(#)JpegImagesToMovie.java 1.3 01/03/13
*
* Copyright (c) 1999-2001 Sun Microsystems, Inc. All Rights Reserved.
*
* Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
* modify and redistribute this software in source and binary code form,
* provided that i) this copyright notice and license appear on all copies of
* the software; and ii) Licensee does not utilize the software in a manner
* which is disparaging to Sun.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
* IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
* NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
* LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
* OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
* LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
* INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
* CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
* OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGES.
*
* This software is not designed or intended for use in on-line control of
* aircraft, air traffic, aircraft navigation or aircraft communications; or in
* the design, construction, operation or maintenance of any nuclear
* facility. Licensee represents and warrants that it will not use or
* redistribute the Software for such purposes.
*/
import java.io.*;
import java.util.*;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.media.*;
import javax.media.control.*;
import javax.media.protocol.*;
import javax.media.datasink.*;
import javax.media.format.RGBFormat;
import javax.media.format.VideoFormat;
/**
* This program takes a list of JPEG image files and convert them into a
* QuickTime movie.
*/
public class JpegImagesToMovie implements ControllerListener, DataSinkListener {
static private Vector<String> getImageFilesPathsVector(
String imagesFolderPath) {
File imagesFolder = new File(imagesFolderPath);
String[] imageFilesArray = imagesFolder.list();
Vector<String> imageFilesPathsVector = new Vector<String>();
for (String imageFileName : imageFilesArray) {
if (!imageFileName.toLowerCase().endsWith("png"))
continue;
imageFilesPathsVector.add(imagesFolder.getAbsolutePath()
+ File.separator + imageFileName);
}
return imageFilesPathsVector;
}
public boolean doIt(int width, int height, int frameRate,
Vector<String> inFiles, MediaLocator outML) {
ImageDataSource ids = new ImageDataSource(width, height, frameRate,
inFiles);
Processor p;
try {
System.err
.println("- create processor for the image datasource ...");
p = Manager.createProcessor(ids);
} catch (Exception e) {
System.err
.println("Yikes! Cannot create a processor from the data source.");
return false;
}
p.addControllerListener(this);
// Put the Processor into configured state so we can set
// some processing options on the processor.
p.configure();
if (!waitForState(p, Processor.Configured)) {
System.err.println("Failed to configure the processor.");
return false;
}
// Set the output content descriptor to QuickTime.
p.setContentDescriptor(new ContentDescriptor(
FileTypeDescriptor.QUICKTIME));// FileTypeDescriptor.MSVIDEO
// Query for the processor for supported formats.
// Then set it on the processor.
TrackControl tcs[] = p.getTrackControls();
Format f[] = tcs[0].getSupportedFormats();
if (f == null || f.length <= 0) {
System.err.println("The mux does not support the input format: "
+ tcs[0].getFormat());
return false;
}
tcs[0].setFormat(f[0]);
System.err.println("Setting the track format to: " + f[0]);
// We are done with programming the processor. Let's just
// realize it.
p.realize();
if (!waitForState(p, Controller.Realized)) {
System.err.println("Failed to realize the processor.");
return false;
}
// Now, we'll need to create a DataSink.
DataSink dsink;
if ((dsink = createDataSink(p, outML)) == null) {
System.err
.println("Failed to create a DataSink for the given output MediaLocator: "
+ outML);
return false;
}
dsink.addDataSinkListener(this);
fileDone = false;
System.err.println("start processing...");
// OK, we can now start the actual transcoding.
try {
p.start();
dsink.start();
} catch (IOException e) {
System.err.println("IO error during processing");
return false;
}
// Wait for EndOfStream event.
waitForFileDone();
// Cleanup.
try {
dsink.close();
} catch (Exception e) {
}
p.removeControllerListener(this);
System.err.println("...done processing.");
return true;
}
/**
* Create the DataSink.
*/
DataSink createDataSink(Processor p, MediaLocator outML) {
DataSource ds;
if ((ds = p.getDataOutput()) == null) {
System.err
.println("Something is really wrong: the processor does not have an output DataSource");
return null;
}
DataSink dsink;
try {
System.err.println("- create DataSink for: " + outML);
dsink = Manager.createDataSink(ds, outML);
dsink.open();
} catch (Exception e) {
System.err.println("Cannot create the DataSink: " + e);
return null;
}
return dsink;
}
Object waitSync = new Object();
boolean stateTransitionOK = true;
/**
* Block until the processor has transitioned to the given state. Return
* false if the transition failed.
*/
boolean waitForState(Processor p, int state) {
synchronized (waitSync) {
try {
while (p.getState() < state && stateTransitionOK)
waitSync.wait();
} catch (Exception e) {
}
}
return stateTransitionOK;
}
/**
* Controller Listener.
*/
public void controllerUpdate(ControllerEvent evt) {
if (evt instanceof ConfigureCompleteEvent
|| evt instanceof RealizeCompleteEvent
|| evt instanceof PrefetchCompleteEvent) {
synchronized (waitSync) {
stateTransitionOK = true;
waitSync.notifyAll();
}
} else if (evt instanceof ResourceUnavailableEvent) {
synchronized (waitSync) {
stateTransitionOK = false;
waitSync.notifyAll();
}
} else if (evt instanceof EndOfMediaEvent) {
evt.getSourceController().stop();
evt.getSourceController().close();
}
}
Object waitFileSync = new Object();
boolean fileDone = false;
boolean fileSuccess = true;
/**
* Block until file writing is done.
*/
boolean waitForFileDone() {
synchronized (waitFileSync) {
try {
while (!fileDone)
waitFileSync.wait();
} catch (Exception e) {
}
}
return fileSuccess;
}
/**
* Event handler for the file writer.
*/
public void dataSinkUpdate(DataSinkEvent evt) {
if (evt instanceof EndOfStreamEvent) {
synchronized (waitFileSync) {
fileDone = true;
waitFileSync.notifyAll();
}
} else if (evt instanceof DataSinkErrorEvent) {
synchronized (waitFileSync) {
fileDone = true;
fileSuccess = false;
waitFileSync.notifyAll();
}
}
}
public static void main(String args[]) {
// changed this method a bit
if (args.length == 0)
prUsage();
// Parse the arguments.
int i = 0;
int width = -1, height = -1, frameRate = -1;
Vector<String> inputFiles = new Vector<String>();
String rootDir = null;
String outputURL = null;
while (i < args.length) {
if (args[i].equals("-w")) {
i++;
if (i >= args.length)
prUsage();
width = new Integer(args[i]).intValue();
} else if (args[i].equals("-h")) {
i++;
if (i >= args.length)
prUsage();
height = new Integer(args[i]).intValue();
} else if (args[i].equals("-f")) {
i++;
if (i >= args.length)
prUsage();
// new Integer(args[i]).intValue();
frameRate = Integer.parseInt(args[i]);
} else if (args[i].equals("-o")) {
i++;
if (i >= args.length)
prUsage();
outputURL = args[i];
} else if (args[i].equals("-i")) {
i++;
if (i >= args.length)
prUsage();
rootDir = args[i];
} else {
System.out.println(".");
prUsage();
}
i++;
}
if (rootDir == null) {
System.out
.println("Since no input (-i) forder provided, assuming this JAR is inside JPEGs folder.");
rootDir = (new File(".")).getAbsolutePath();
}
inputFiles = getImageFilesPathsVector(rootDir);
if (inputFiles.size() == 0)
prUsage();
if (outputURL == null) {
outputURL = (new File(rootDir)).getAbsolutePath() + File.separator
+ "pngs2movie.mov";
}
if (!outputURL.toLowerCase().startsWith("file:///")) {
outputURL = "file:///" + outputURL;
}
// Check for output file extension.
if (!outputURL.toLowerCase().endsWith(".mov")) {
prUsage();
outputURL += ".mov";
System.out
.println("outputURL should be ending with mov. Making this happen.\nNow outputURL is: "
+ outputURL);
}
if (width < 0 || height < 0) {
prUsage();
System.out.println("Trying to guess movie size from first image");
BufferedImage firstImageInFolder = getFirstImageInFolder(rootDir);
width = firstImageInFolder.getWidth();
height = firstImageInFolder.getHeight();
System.out.println("width = " + width);
System.out.println("height = " + height);
}
// Check the frame rate.
if (frameRate < 1)
frameRate = 30;
// Generate the output media locators.
MediaLocator oml;
if ((oml = createMediaLocator(outputURL)) == null) {
System.err.println("Cannot build media locator from: " + outputURL);
System.exit(0);
}
JpegImagesToMovie imageToMovie = new JpegImagesToMovie();
imageToMovie.doIt(width, height, frameRate, inputFiles, oml);
System.exit(0);
}
private static BufferedImage getFirstImageInFolder(String rootDir) {
File rootFile = new File(rootDir);
String[] list = (rootFile).list();
BufferedImage bufferedImage = null;
for (String filePath : list) {
if (!filePath.toLowerCase().endsWith(".png")
&& !filePath.toLowerCase().endsWith(".png")) {
continue;
}
try {
bufferedImage = ImageIO.read(new File(rootFile
.getAbsoluteFile() + File.separator + filePath));
break;
} catch (IOException e) {
e.printStackTrace();
}
}
return bufferedImage;
}
static void prUsage() {
System.err
.println("Usage: java JpegImagesToMovie [-w <width>] [-h <height>] [-f <frame rate>] [-o <output URL>] -i <input JPEG files dir Path>");
// System.exit(-1);
}
/**
* Create a media locator from the given string.
*/
#SuppressWarnings("unused")
public static MediaLocator createMediaLocator(String url) {
MediaLocator ml;
if (url.indexOf(":") > 0 && (ml = new MediaLocator(url)) != null)
return ml;
if (url.startsWith(File.separator)) {
if ((ml = new MediaLocator("file:" + url)) != null)
return ml;
} else {
String file = "file:" + System.getProperty("user.dir")
+ File.separator + url;
if ((ml = new MediaLocator(file)) != null)
return ml;
}
return null;
}
// /////////////////////////////////////////////
//
// Inner classes.
// /////////////////////////////////////////////
/**
* A DataSource to read from a list of JPEG image files and turn that into a
* stream of JMF buffers. The DataSource is not seekable or positionable.
*/
class ImageDataSource extends PullBufferDataSource {
ImageSourceStream streams[];
ImageDataSource(int width, int height, int frameRate,
Vector<String> images) {
streams = new ImageSourceStream[1];
streams[0] = new PngImageSourceStream(width, height, frameRate, images);
}
public void setLocator(MediaLocator source) {
}
public MediaLocator getLocator() {
return null;
}
/**
* Content type is of RAW since we are sending buffers of video frames
* without a container format.
*/
public String getContentType() {
return ContentDescriptor.RAW;
}
public void connect() {
}
public void disconnect() {
}
public void start() {
}
public void stop() {
}
/**
* Return the ImageSourceStreams.
*/
public PullBufferStream[] getStreams() {
return streams;
}
/**
* We could have derived the duration from the number of frames and
* frame rate. But for the purpose of this program, it's not necessary.
*/
public Time getDuration() {
return DURATION_UNKNOWN;
}
public Object[] getControls() {
return new Object[0];
}
public Object getControl(String type) {
return null;
}
}
/**
* The source stream to go along with ImageDataSource.
*/
class ImageSourceStream implements PullBufferStream {
Vector<String> images;
int width, height;
VideoFormat format;
int nextImage = 0; // index of the next image to be read.
boolean ended = false;
public ImageSourceStream(int width, int height, int frameRate,
Vector<String> images) {
this.width = width;
this.height = height;
this.images = images;
format = new VideoFormat(VideoFormat.JPEG, new Dimension(width,
height), Format.NOT_SPECIFIED, Format.byteArray,
(float) frameRate);
}
/**
* We should never need to block assuming data are read from files.
*/
public boolean willReadBlock() {
return false;
}
/**
* This is called from the Processor to read a frame worth of video
* data.
*/
public void read(Buffer buf) throws IOException {
// Check if we've finished all the frames.
if (nextImage >= images.size()) {
// We are done. Set EndOfMedia.
System.err.println("Done reading all images.");
buf.setEOM(true);
buf.setOffset(0);
buf.setLength(0);
ended = true;
return;
}
String imageFile = (String) images.elementAt(nextImage);
nextImage++;
System.err.println(" - reading image file: " + imageFile);
// Open a random access file for the next image.
RandomAccessFile raFile;
raFile = new RandomAccessFile(imageFile, "r");
byte data[] = null;
// Check the input buffer type & size.
if (buf.getData() instanceof byte[])
data = (byte[]) buf.getData();
// Check to see the given buffer is big enough for the frame.
if (data == null || data.length < raFile.length()) {
data = new byte[(int) raFile.length()];
buf.setData(data);
}
// Read the entire JPEG image from the file.
raFile.readFully(data, 0, (int) raFile.length());
System.err.println(" read " + raFile.length() + " bytes.");
buf.setOffset(0);
buf.setLength((int) raFile.length());
buf.setFormat(format);
buf.setFlags(buf.getFlags() | Buffer.FLAG_KEY_FRAME);
// Close the random access file.
raFile.close();
}
/**
* Return the format of each video frame. That will be JPEG.
*/
public Format getFormat() {
return format;
}
public ContentDescriptor getContentDescriptor() {
return new ContentDescriptor(ContentDescriptor.RAW);
}
public long getContentLength() {
return 0;
}
public boolean endOfStream() {
return ended;
}
public Object[] getControls() {
return new Object[0];
}
public Object getControl(String type) {
return null;
}
}
class PngImageSourceStream extends ImageSourceStream {
public PngImageSourceStream(int width, int height, int frameRate, Vector<String> images) {
super(width, height, frameRate, images);
// configure the new format as RGB format
format = new RGBFormat(new Dimension(width, height), Format.NOT_SPECIFIED, Format.byteArray, frameRate,
24, // 24 bits per pixel
1, 2, 3); // red, green and blue masks when data are in the form of byte[]
}
public void read(Buffer buf) throws IOException {
// Check if we've finished all the frames.
if (nextImage >= images.size()) {
// We are done. Set EndOfMedia.
System.err.println("Done reading all images.");
buf.setEOM(true);
buf.setOffset(0);
buf.setLength(0);
ended = true;
return;
}
String imageFile = (String) images.elementAt(nextImage);
nextImage++;
System.err.println(" - reading image file: " + imageFile);
// read the PNG image
BufferedImage image = ImageIO.read( new File(imageFile) );
Dimension size = format.getSize();
// convert 32-bit RGBA to 24-bit RGB
byte[] imageData = convertTo24Bit(image.getRaster().getPixels(0, 0, size.width, size.height, (int[]) null));
buf.setData(imageData);
System.err.println(" read " + imageData.length + " bytes.");
buf.setOffset(0);
buf.setLength(imageData.length);
buf.setFormat(format);
buf.setFlags(buf.getFlags() | Buffer.FLAG_KEY_FRAME);
}
private void convertIntByteToByte(int[] src, int srcIndex, byte[] out, int outIndex) {
// Note: the int[] returned by bufferedImage.getRaster().getPixels() is an int[]
// where each int is the value for one color i.e. the first 4 ints contain the RGBA values for the first pixel
int r = src[srcIndex];
int g = src[srcIndex+1];
int b = src[srcIndex+2];
out[outIndex] = (byte) (r & 0xFF);
out[outIndex+1] = (byte) (g & 0xFF);
out[outIndex+2] = (byte) (b & 0xFF);
}
private byte[] convertTo24Bit(int[] input) {
int dataLength = input.length;
byte[] convertedData = new byte[ dataLength * 3 / 4 ];
// for every 4 int values of the original array (RGBA) write 3
// bytes (RGB) to the output array
for (int i = 0, j = 0; i < dataLength; i+=4, j+=3) {
convertIntByteToByte(input, i, convertedData, j);
}
return convertedData;
}
}
}
I make the video using the following call in my main method
r.makeVideo("Video.mov");
And here is that method.
public void makeVideo (String movFile) throws MalformedURLException {
JpegImagesToMovie imageToMovie = new JpegImagesToMovie();
Vector<String> imgList = new Vector <String>();
File f = new File("C:\\Users\\user\\Desktop\\tmp\\");
File[] fileList = f.listFiles();
for (int i = 0; i < fileList.length; i++) {
imgList.add(fileList[i].getAbsolutePath());
}
MediaLocator ml;
if ((ml = imageToMovie.createMediaLocator(movFile)) == null) {
System.exit(0);
}
imageToMovie.doIt(width, height, (1000/125), imgList, ml);
}
Error when running:
Usage: java JpegImagesToMovie [-w ] [-h ] [-f ] [-o ] -i
Since no input (-i) forder provided, assuming this JAR is inside JPEGs folder.
Usage: java JpegImagesToMovie [-w ] [-h ] [-f ] [-o ] -i
Usage: java JpegImagesToMovie [-w ] [-h ] [-f ] [-o ] -i
Trying to guess movie size from first image
Exception in thread "main" java.lang.NullPointerException
at maple.JpegImagesToMovie.main(JpegImagesToMovie.java:342)
line 342
width = firstImageInFolder.getWidth();
Though a little late (since the question is already answered), and because coincidentally I have worked with ImageIO in the past few days, I will just add here my answer. Especially the second part of how to make JpegImagesToMovie to work with png files has not been answered and it might help someone else.
Double compression issue: As correctly identified by others, you are effectively compressing the JPEG image twice by using ImageIO.write(). The ImageIO is a utility class that based on the type of the file ("jpeg" in our case) chooses an appropriate ImageWriter. It then constructs an ImageInputStream and it passes it to the writer. Last it calls its write() method. Pretty much what #meewoK does in his answer. However every ImageWriter can take a ImageWriteParam instance that configures its details. ImageIO apparently cannot know what parameters each writer can accept, nor how it should be configured, so default settings are used. If you look at the source JPEGImageWriteParam has a default quality factor of 0.75, so you are effectively multiplying this factor with whatever quality used for the original file. (If it was 0.75 again, your final image has pretty much a 0.75 * 0.75 = 0.56 quality i.e. you lost the half of the original). Bottom line: Use ImageIO for quick reads or writes but if more control is desired the recommended approach is to configure and use an ImageWriter manually.
How to make JpegImagesToMovie to work directly with PNG files: If you look at the source of that class all the work is done in its ImageSourceStream inner class. What it does, is that it loads the bytes from the files (each file is a video frame) directly to a Processor class that creates the video. Conveniently the Processor knows how to handle JPEG format directly (look at the stream's constructor format = new VideoFormat(VideoFormat.JPEG, ...)), so feeding it with the raw file data works as expected.
To make it work with PNG format though, just replacing the file suffix as per your approach is not enough. You need to convert the PNG data to a format that Processor understands (i.e. to decode it). Below you can find a quick and dirty way to do that. Disclaimer: the approach below uses more memory since it loads the image in memory and it further manipulates it to convert it to a byte[]. So performance-wise and memory-wise is worst. However if memory and speed is not a concern, it would allow you to work directly with PNG files.
Conversion steps:
a) With an editor search and replace the string values "jpeg", "jpg" with "png". The original author has those values hard-coded and during your attempt you have missed some entries.
b) In the constructor of the ImageDataSource replace the first line with the second one :
streams[0] = new ImageSourceStream(width, height, frameRate, images); // delete this line
streams[0] = new PngImageSourceStream(width, height, frameRate, images); // add this line
c) At the end of the class add the implementation of the new PngImageSourceStream provided below.
You should now have a working copy of the original version that is able to read PNG files directly and convert them to a MOV video (Note: Not all players can play that new video because of the codec used. QuickTime and Media Player Classic worked ok for me)
Update 1: The original code of PngImageSourceStreamassumed a 32-bit PNG file, a restriction I forgot to mention. The version below is a second version that supports either 32-bit or 24-bit (i.e. with no alpha layer) images.
class PngImageSourceStream extends ImageSourceStream {
public PngImageSourceStream(int width, int height, int frameRate,
Vector<String> images) {
super(width, height, frameRate, images);
// configure the new format as RGB format
format = new RGBFormat(new Dimension(width, height),
Format.NOT_SPECIFIED, Format.byteArray, frameRate,
24, // 24 bits per pixel
1, 2, 3); // red, green and blue masks when data are in the form of byte[]
}
public void read(Buffer buf) throws IOException {
// Check if we've finished all the frames.
if (nextImage >= images.size()) {
// We are done. Set EndOfMedia.
System.err.println("Done reading all images.");
buf.setEOM(true);
buf.setOffset(0);
buf.setLength(0);
ended = true;
return;
}
String imageFile = (String) images.elementAt(nextImage);
nextImage++;
System.err.println(" - reading image file: " + imageFile);
// read the PNG image
BufferedImage image = ImageIO.read(new File(imageFile));
boolean hasAlpha = image.getColorModel().hasAlpha();
Dimension size = format.getSize();
// convert 32-bit RGBA to 24-bit RGB
byte[] imageData = convertTo24Bit(hasAlpha, image.getRaster().getPixels(0, 0, size.width, size.height, (int[]) null));
buf.setData(imageData);
System.err.println(" read " + imageData.length + " bytes.");
buf.setOffset(0);
buf.setLength(imageData.length);
buf.setFormat(format);
buf.setFlags(buf.getFlags() | Buffer.FLAG_KEY_FRAME);
}
private void convertIntByteToByte(int[] src, int srcIndex, byte[] out, int outIndex) {
// Note: the int[] returned by bufferedImage.getRaster().getPixels()
// is an int[]
// where each int is the value for one color i.e. the first 4 ints
// contain the RGBA values for the first pixel
int r = src[srcIndex];
int g = src[srcIndex + 1];
int b = src[srcIndex + 2];
out[outIndex] = (byte) (r & 0xFF);
out[outIndex + 1] = (byte) (g & 0xFF);
out[outIndex + 2] = (byte) (b & 0xFF);
}
private byte[] convertTo24Bit(boolean hasAlpha, int[] input) {
int dataLength = input.length;
int newSize = (hasAlpha ? dataLength * 3 / 4 : dataLength);
byte[] convertedData = new byte[newSize];
// for every 4 int values of the original array (RGBA) write 3
// bytes (RGB) to the output array
// if there is no alpha (i.e. RGB image) then just convert int to byte
for (int i = 0, j = 0; i < dataLength; i += 3, j += 3) {
convertIntByteToByte(input, i, convertedData, j);
if (hasAlpha) {
i++; // skip an extra byte if the original image has an
// extra int for transparency
}
}
return convertedData;
}
}
Set the JPEG compression level1 to a higher quality. This will result in a larger file size, but should fix the problem of quality.
But encode them only as:
ImageIO.write(img, "jpg", new File("..." + ".jpg"));
As seen in this answer - screenshot below.
Compile and run the code in the linked answer, drag the Slider on the left up and down to see the result of that compression level in the JPEG (bottom/3rd image). The text area at the bottom will show the size in bytes at that level of 'quality'. The quality is inverse to the compression, and as you might notice from the images the JPEG at %80 quality is not only a bit 'murky', but is already significantly larger in bytes than the PNG.
Then have a careful look at the code, especially:
private Image getJpegCompressedImage(BufferedImage image) throws IOException {
float qualityFloat = (float)quality.getValue()/100f;
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
ImageWriter imgWriter = ImageIO.getImageWritersByFormatName( "jpg" ).next();
ImageOutputStream ioStream = ImageIO.createImageOutputStream( outStream );
imgWriter.setOutput( ioStream );
JPEGImageWriteParam jpegParams = new JPEGImageWriteParam( Locale.getDefault() );
jpegParams.setCompressionMode( ImageWriteParam.MODE_EXPLICIT );
jpegParams.setCompressionQuality( qualityFloat ); // Set the compression level
imgWriter.write( null, new IIOImage( image, null, null ), jpegParams );
ioStream.flush();
ioStream.close();
imgWriter.dispose();
jpgSize = outStream.toByteArray().length;
BufferedImage compressedImage = ImageIO.read(new ByteArrayInputStream(outStream.toByteArray()));
return compressedImage;
}
I'm taking a stab at this by bypassing ImgIO.write() and directly writing the bytes to the file using a FileOutputStream ...
I'm bypasing ImgIO.write due to numerous mentions on the internet saying imagequality cannot be controlled through this method.
Quoting from here: http://www.universalwebservices.net/web-programming-resources/java/adjust-jpeg-image-compression-quality-when-saving-images-in-java
If you've been using the imageIO.write method to save JPEG images, you may notice that some image lose quality. This is because you can't instruct the imagIO.write method to apply a certain compression quality to the images.
My first Stab:
import java.awt.image.BufferedImage;
public class Quality {
public static void main (String[] args) {
BufferedImage img;
try {
File f = new File("test" + ".jpg");
img = ImageIO.read(f);
writeJpegCompressedImage(img,"NEW" + ".jpg" );
} catch (IOException e) {
e.printStackTrace();
}
}
private static void writeJpegCompressedImage(BufferedImage image, String outFile) throws IOException {
float qualityFloat = 1f;
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
ImageWriter imgWriter = ImageIO.getImageWritersByFormatName( "jpg" ).next();
ImageOutputStream ioStream = ImageIO.createImageOutputStream( outStream );
imgWriter.setOutput( ioStream );
JPEGImageWriteParam jpegParams = new JPEGImageWriteParam( Locale.getDefault() );
jpegParams.setCompressionMode( ImageWriteParam.MODE_EXPLICIT );
jpegParams.setCompressionQuality( qualityFloat );
imgWriter.write( null, new IIOImage( image, null, null ), jpegParams );
ioStream.flush();
ioStream.close();
imgWriter.dispose();
OutputStream outputStream = new FileOutputStream ( outFile);
outStream.writeTo(outputStream);
}
}