Okay, so the basic problems you will face. Most image formats employee some kind of compression on the image data, they may also append important data about the image to the end of the file, such as the color model information, which makes rendering them as they are read some what difficult.
What you need is some way to write "chunks" of the image to a file that can be easily read back but does not dramatically increase the file size.
My test image started at 301.68 kb and my "chunk" file format end up as 1.42 mb, which I wasn't particularly happy with until I tested a uncompressed file which ended up at 5.63 mb...think I can live for the momement.
The example uses the inbuilt GZip
compression, you can get better compression by using something like Apache-Commons-Compress
On paper, what this basically does...
- Reads a chunk of the pixel data, writing this to a comma-separated
String
, where it each value is a given pixel value from the image. The example reads 10% of the file per chunk.
- Each chunk is then compressed using a
GZip
compression
- The resulting compressed bytes are then encoded using a
Base64
encoding. Personally I'd prefer to use Apache-Commons-Encode
as it places less relience on an internal/private class.
- The resulting encoded
String
is then written to a File
and a new line is placed at the end of the line.
The image is loaded in reverse...
- A line is read from the file (Base64 encoded
String
)
- The
String
is decoded (to a compressed byte
array)
- The
byte
array is then decompressed into a comma separated String
- The comma separated
String
is then split
and the resulting pixel data is drawn to a backing buffer
- The resulting backing buffer is updated to the screen...
The theory is all good, the implementation is...a little messy, sorry, could be a little tidier, but you get the idea.
The intention with this idea would be to not read the entire Image.dat
file at once, but instead, leaving it where it is and read a line at a time...this allows for latency.
Now, in this example, I've used a javax.swing.Timer
to inject a little pause, to be honest, it would be better to use a SwingWorker
...but I'm sure you get the idea...
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ConvertImage {
public static void main(String[] args) {
try {
exportImage(new File("/path/to/your/image.jpg"), new File("Image.dat"));
} catch (IOException ex) {
ex.printStackTrace();
}
new ConvertImage();
}
public ConvertImage() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private int imgWidth = 0;
private int imgHeight = 0;
private BufferedReader br = null;
private BufferedImage imgBuffer;
private int offset;
public TestPane() {
try {
br = new BufferedReader(new FileReader(new File("Image.dat")));
String header = br.readLine();
String[] parts = header.split("x");
imgWidth = Integer.parseInt(parts[0]);
imgHeight = Integer.parseInt(parts[1]);
imgBuffer = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_ARGB);
Timer timer = new Timer(1000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Graphics2D g2d = null;
try {
String text = br.readLine();
if (text != null) {
// Decode the String back to a compressed byte array
byte[] decode = Base64.decode(text);
GZIPInputStream zis = null;
try {
// Decompress the byte array
zis = new GZIPInputStream(new ByteArrayInputStream(decode));
// Build the text representation of the pixels
StringBuilder sb = new StringBuilder(128);
byte[] buffer = new byte[1024];
int bytesRead = -1;
while ((bytesRead = zis.read(buffer)) > -1) {
sb.append(new String(buffer, 0, bytesRead, "UTF-8"));
}
// Split the pixels into individual packed ints
String[] elements = sb.toString().split(",");
g2d = imgBuffer.createGraphics();
for (String element : elements) {
Point p = getPointAt(offset, imgWidth, imgHeight);
g2d.setColor(new Color(Integer.parseInt(element), true));
g2d.drawLine(p.x, p.y, p.x, p.y);
offset++;
}
g2d.dispose();
repaint();
} catch (Exception exp) {
exp.printStackTrace();
}
} else {
try {
br.close();
} catch (Exception exp) {
}
((Timer) e.getSource()).stop();
}
} catch (IOException ex) {
ex.printStackTrace();
try {
br.close();
} catch (Exception exp) {
}
((Timer) e.getSource()).stop();
} finally {
try {
g2d.dispose();
} catch (Exception exp) {
}
}
}
});
timer.start();
} catch (IOException ex) {
ex.printStackTrace();
try {
br.close();
} catch (Exception e) {
}
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(imgWidth, imgHeight);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int x = (getWidth() - imgBuffer.getWidth()) / 2;
int y = (getHeight() - imgBuffer.getHeight()) / 2;
g.drawImage(imgBuffer, x, y, this);
}
}
protected static void exportImage(File in, File out) throws IOException {
BufferedImage img = ImageIO.read(in);
int width = img.getWidth();
int height = img.getHeight();
// Calculate the total "length" of the image
int imageLength = width * height;
// Calculate the length of each line we will produce
// This is the number of pixels per chunk
int runLength = Math.round((width * height) * 0.1f);
// The place to write the output
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new FileWriter(out));
bw.write(width + "x" + height);
bw.newLine();
// Start converting the pixels...
int offset = 0;
while (offset < imageLength) {
// Calculate the size of the next buffer run, we don't want to
// over run the end of the image
int bufferSize = runLength;
if (offset + bufferSize > imageLength) {
bufferSize = imageLength - offset;
}
// Create a buffer to store the pixel results in...
StringBuilder sb = new StringBuilder(bufferSize * 2);
for (int index = 0; index < bufferSize; index++) {
Point p = getPointAt(offset + index, width, height);
if (sb.length() > 0) {
sb.append(",");
}
// Store the pixel
sb.append(img.getRGB(p.x, p.y));
}
// Write the contents to a compressed stream...
ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream zos = new GZIPOutputStream(baos);
zos.write(sb.toString().getBytes());
zos.flush();
zos.close();
// Encode the compressed results to Base64
String encoded = Base64.encode(baos.toByteArray());
// Write the content...
bw.write(encoded);
bw.newLine();
// Jump to the next "chunk"
offset += bufferSize;
}
} catch (IOException exp) {
exp.printStackTrace();
} finally {
try {
bw.close();
} catch (Exception e) {
}
}
}
public static Point getPointAt(int i