GSON can't serialize BufferedImages - java

There seems to be a problem with serializing BufferedImages in JSON using GSON. I am using Derby to store images. When I query the the database I build a JavaBean that has some text fields and one BufferedImage field. I then use GSON to convert the JavaBean into JSON, and this is where the exception occurs.
The Exception message is below:
java.lang.IllegalArgumentException: class sun.awt.image.ByteInterleavedRaster declares multiple JSON fields named maxX
I did find similar problems here GSON java.lang.IllegalArgumentException: class 'xx' declares multiple JSON fields named 'XX' AND StackOverflowError and here class A declares multiple JSON fields
But the problem is with the awt library included with Java. I could follow the answers provided in those other stackoverflow answers if I could access the AWT source code, but how do I do that?

You have to know that not every class is designed to be (de)serialized, especially if (de)serialization is based on the target class binary structure. Your approach has at least the following weak points:
the sun.awt.image.ByteInterleavedRaster class fields are not necessarily the same on another JVM/JRE, thus you can get be vendor-locked;
persisting binary data in JSON is probably not the best choice (probably huge and terrible memory consumption during (de)serialization, storage consumption, performance) -- maybe a generic blob storage is better for binary data?
reading an images with Java AWT and writing it back does not guarantee the same binary output: for example, my test image, 1.2K, was deserialized as an image of another size, 0.9K;
you must choose the target persisting image format or detect the most efficient one (how?).
Consider the following simple class:
final class ImageHolder {
final RenderedImage image;
ImageHolder(final RenderedImage image) {
this.image = image;
}
}
Now you have to create a type adapter to tell Gson how a particular type instance can be stored and restored:
final class RenderedImageTypeAdapter
extends TypeAdapter<RenderedImage> {
private static final TypeAdapter<RenderedImage> renderedImageTypeAdapter = new RenderedImageTypeAdapter().nullSafe();
private RenderedImageTypeAdapter() {
}
static TypeAdapter<RenderedImage> getRenderedImageTypeAdapter() {
return renderedImageTypeAdapter;
}
#Override
#SuppressWarnings("resource")
public void write(final JsonWriter out, final RenderedImage image)
throws IOException {
// Intermediate buffer
final ByteArrayOutputStream output = new ByteArrayOutputStream();
// By the way, how to pick up the target image format? BMP takes more space, PNG takes more time, JPEG is lossy...
ImageIO.write(image, "PNG", output);
// Not sure about this, but converting to base64 is more JSON-friendly
final Base64.Encoder encoder = Base64.getEncoder();
// toByteArray() returns a copy, not the original array (x2 more memory)
// + creating a string requires more memory to create the String internal buffer (x3 more memory)
final String imageBase64 = encoder.encodeToString(output.toByteArray());
out.value(imageBase64);
}
#Override
public RenderedImage read(final JsonReader in)
throws IOException {
// The same in reverse order
final String imageBase64 = in.nextString();
final Base64.Decoder decoder = Base64.getDecoder();
final byte[] input = decoder.decode(imageBase64);
return ImageIO.read(new ByteArrayInputStream(input));
}
}
Note that Gson is currently NOT very well designed to support byte transformation, however it might be somewhat better in the future if fixed.
Example use:
private static final Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter(RenderedImage.class, getRenderedImageTypeAdapter())
.create();
public static void main(final String... args)
throws IOException {
try ( final InputStream inputStream = getPackageResourceInputStream(Q43301580.class, "sample.png") ) {
final RenderedImage image = ImageIO.read(inputStream);
final ImageHolder before = new ImageHolder(image);
final String json = gson.toJson(before);
System.out.println(json);
final ImageHolder after = gson.fromJson(json, ImageHolder.class);
...
}
}
Example output (with real tiny (32x32) PNG file inside):
{"image":"iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADgklEQVR42t2XXUiTYRTHpxj4kSKShhgYGSihZGIXXYhU5J2BhBIhCH5cCF6oiWhG0k1BpHghgRgoJHiloBKEqFQ3frDNuemaOqdu0+n8mFM3Nzf37z1n+JZUEPlOoQdetvd5L87vOed/Ph4ZznnJzsqQz+uFz+M5HwBrezuUFy9CERoKY3U1jtzuwAFY29pgGxgQ350aDVSXLmFfLud9eVAQTHV1gQNYKi+HMiwM9uFhft/o6MBcTg6fWp+XB93duzhyOOA7POSwyAIR64UnTxhi9+tXfhQhIdBlZ2P2wQM2Tmv11StY3rwJjAYIQl9QAGVUFPZGRzF7/z7kwcGw9ffzt80PHzAZE4ODuTnpAQ50OjgmJ3HkcmE+N5chdr98wfzDh5DLZPyo4uOx+/mz9Bqg+B8b0d6+zSecFeJPInSo1XAbjXAKvxR/yUW4Pz7uV/vEBJ9OffUqNNev49BiYeGp4uLg0usDUwdIUNNpaTDV1op7rqUljvNKYyMLb7G4GIdWa2AAbH19LDIy8vNaefmSBRiQUkynMtXUYLGkBO7lZWx2dTEEnVjURFnZL1CSASyWlmL6xg1okpIwdeUK3CYTNjo7WYCGoiLOeU1yMtxmc2AA1NeuscA829uYTk1lEIJYf/eOIcgzP6tdEgAyRicjtatiY8V9EhdDpKTw/7XmZoYgGEkBzEITIQDzs2dsYPX1a/EbuZq8YG5o8GeG8E2dmIgjp/P0AJxGgku1GRnYVyh479jVdFrRE+vrXGqPl3dvTxoPeO12aDMz2aBDqRT315qa/trV/wTgsdmw1d3NJVSMs+BmOqlYhARXL1dUSA/gWljg9FKGh/u72tgYQ1BqEcjvqtqpAHY+fcLOx4/+durzcTOxvH3LXY1qOUFQ/CnVyAszN2+eGK1OBWCur4cyIgIrL174Xb+1hdl79xiERioqOFRSKf3sQ0MclvXWVmk8sN3b6+9UBsMvQwWtb3fuwD4ywpkwlZDAojNWVUk3lhsrK7Hw+PHJ+AudzKnVwrOzwwYP5ud50JhJT5cs9iLAxvv3UFy4wLVdn58P1eXLP4YKIfWor09GR0MZGYm1lhbpLyYUZ/Pz55i5dQu6rCwYnz4FhYXmNjJKKbYmiHG7p+fsb0aGwkIsC2PWuVzNaJ5j1Q8Oni0AVTkKCbmffs/8cuoVlK9/9IjHrP/qdvyn9R0SEM4flWsmCwAAAABJRU5ErkJggg\u003d\u003d"}
I think there are too many flaws, and I would strongly recommend you to redesign your binaries storage if possible and store binary content as-is.

Related

Transferring and saving MultipartFile instance

I have the following method, with the simple aim to store the contents of a given MultipartFile instance under a specified directory:
private void saveOnDisk(final String clientProductId, final MultipartFile image, final String parentDirectoryPath, final String fileSeparator) throws IOException
{
final File imageFile = new File(parentDirectoryPath + fileSeparator + clientProductId + image.getOriginalFilename());
image.transferTo(imageFile);
OutputStream out = new FileOutputStream(imageFile);
out. //... ? How do we proceed? OutputStream::write() requires a byte array or int as parameter
}
For what it might be worth, the MultipartFile instance is going to contain an image file which I receive on a REST API I'm building.
I've checked some SO posts such as this and this but this problem is not quite touched: I'm effectively looking to create an entirely new image file and store it on a specified location on disk: the method write() of OutputStream, given that it requires byte[] or int params, doesn't seem to be fitting my use case. Any ideas?

Obtaining Image object in Java object from .NET System.DrawingImage with Javonet

I have a question about using Javonet. In the .NET code I have a method that returns a
System.DrawingImage and I have to obtain it on my Java side of code. So
when I issue a "get" of the attribute by Javonet API, it returns an
NObject. Here is the example:
.NET code:
...
namespace ImageTest
{
public class FileReader
{
public byte[] GetByteArray(string filename)
{
return System.IO.File.ReadAllBytes(filename);
}
public System.Drawing.Image GetImage(string filename)
{
byte[] b = GetByteArray(filename);
using (var ms = new System.IO.MemoryStream(b, false))
{
return Image.FromStream(ms);
}
}
}
}
Java code
...
public Image getImage(String fileName) {
NObject res = null;
try {
res = item.get(propName); // Casting NObject to Image is not allowed!
} catch (JavonetException jex) {
jex.printStackTrace();
}
return res; // Wrong: an Image object must be returned!
}
I have to cast 'res' to Image or ImageIcon or something similar in
Java. Any suggestion? Have I to get it as byte array or something else
or there is another mechanism on Javonet?
Any complex object (like class) will always be returned in form of "NObject" so in fact it is the handle to that object on .NET side. If you want to move the concrete image from .NET to Java you need to do it using the value-types so in form of byte[] or string base64 as there is no direct counterpart of "System.Drawing.Image" class in Java.
Therefore you just need to extract the value-type form of your image from System.Drawing.Image class.
You can do it like this:
NObject ms = Javonet.New("MemoryStream");
yourImageNObject.Save(ms,Javonet.getType("System.Drawing.Imaging.ImageFormat").getRef("Jpeg"));
Byte[] imageBytes = ms.invoke("ToArray");
//you can also unbox the bytes to regular byte[]
byte[] unboxed = new byte[fileBytes.length];
for(int i=0; i<imageBytes.length;i++)
unboxed[i]=imageBytes[i].byteValue();

Read PDVInputStream dicomObject information on onCStoreRQ association request

I am trying to read (and then store to 3rd party local db) certain DICOM object tags "during" an incoming association request.
For accepting association requests and storing locally my dicom files i have used a modified version of dcmrcv() tool. More specifically i have overriden onCStoreRQ method like:
#Override
protected void onCStoreRQ(Association association, int pcid, DicomObject dcmReqObj,
PDVInputStream dataStream, String transferSyntaxUID,
DicomObject dcmRspObj)
throws DicomServiceException, IOException {
final String classUID = dcmReqObj.getString(Tag.AffectedSOPClassUID);
final String instanceUID = dcmReqObj.getString(Tag.AffectedSOPInstanceUID);
config = new GlobalConfig();
final File associationDir = config.getAssocDirFile();
final String prefixedFileName = instanceUID;
final String dicomFileBaseName = prefixedFileName + DICOM_FILE_EXTENSION;
File dicomFile = new File(associationDir, dicomFileBaseName);
assert !dicomFile.exists();
final BasicDicomObject fileMetaDcmObj = new BasicDicomObject();
fileMetaDcmObj.initFileMetaInformation(classUID, instanceUID, transferSyntaxUID);
final DicomOutputStream outStream = new DicomOutputStream(new BufferedOutputStream(new FileOutputStream(dicomFile), 600000));
//i would like somewhere here to extract some TAGS from incoming dicom object. By trying to do it using dataStream my dicom files
//are getting corrupted!
//System.out.println("StudyInstanceUID: " + dataStream.readDataset().getString(Tag.StudyInstanceUID));
try {
outStream.writeFileMetaInformation(fileMetaDcmObj);
dataStream.copyTo(outStream);
} finally {
outStream.close();
}
dicomFile.renameTo(new File(associationDir, dicomFileBaseName));
System.out.println("DICOM file name: " + dicomFile.getName());
}
#Override
public void associationAccepted(final AssociationAcceptEvent associationAcceptEvent) {
....
#Override
public void associationClosed(final AssociationCloseEvent associationCloseEvent) {
...
}
I would like somewhere between this code to intercept a method wich will read dataStream and will parse specific tags and store to a local database.
However wherever i try to put a piece of code that tries to manipulate (just read for start) dataStream then my dicom files get corrupted!
PDVInputStream is implementing java.io.InputStream ....
Even if i try to just put a:
System.out.println("StudyInstanceUID: " + dataStream.readDataset().getString(Tag.StudyInstanceUID));
before copying datastream to outStream ... then my dicom files are getting corrupted (1KB of size) ...
How am i supposed to use datastream in a CStoreRQ association request to extract some information?
I hope my question is clear ...
The PDVInputStream is probably a PDUDecoder class. You'll have to reset the position when using the input stream multiple times.
Maybe a better solution would be to store the DICOM object in memory and use that for both purposes. Something akin to:
DicomObject dcmobj = dataStream.readDataset();
String whatYouWant = dcmobj.get( Tag.whatever );
dcmobj.initFileMetaInformation( transferSyntaxUID );
outStream.writeDicomFile( dcmobj );

How to convert byte array to buffered image

I have a server-side java code that gets a byte array from the client. In order to do some image processing, I need to convert the byte array into a BufferedImage. I have a code that's supposed to do that here:
public void processImage(byte[] data) {
ByteArrayInputStream stream = new ByteArrayInputStream(data);
BufferedImage bufferedImage;
bufferedImage = ImageIO.read(stream);
// bufferedImage is null
//...
}
But this doesn't work; bufferedImage is null. According to the ImageIO documentation:
If no registered ImageReader claims to be able to read the resulting stream, null is returned.
How do I tell the ImageReader what image type it is. For instance, if I know the image to be JPEG (which it is, in my case), what am I supposed to do?
EDIT: Thanks for the suggestion that the file is most likely not in JPEG format. This is the client-side code I have that sends the data as String over to the server:
import org.json.JSONObject;
// Client-side code that sends image to server as String
public void sendImage() {
FileInputStream inputStream = new FileInputStream(new File("myImage.jpg"));
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
byte[] b = new byte[1024];
while ((bytesRead = inputStream.read(b)) != -1) {
byteStream.write(b,0,bytesRead);
}
byte[] byteArray = byteStream.toByteArray();
JSONObject jsonObject = new JSONObject();
jsonObject.put("data",new String(byteArray));
// ... more code here that sends jsonObject in HTTP post body
}
And this is the server-side code that calls the processImage() function:
// Server-side code that calls processImage() function
public void handleRequest(String jsonData) {
JSONObject jsonObject = new JSONObject(jsonData);
processImage(jsonObject.getString("data").getBytes());
}
The most likely explanation is that the byte array doesn't contain a JPEG image. (For instance, if you've just attempted to download it, you may have an HTML document giving an error diagnostic.) If that's the case, you'll need to find what is causing this and fix it.
However, if you "know" that the byte array contains an image with a given format, you could do something like this:
Use ImageIO.getImageReadersByFormatName or ImageIO.getImageReadersByMIMEType to get an Iterator<ImageReader>.
Pull the first ImageReader from the Iterator.
Create an MemoryCacheImageInputStream wrapping a ByteArrayInputStream for the types.
Use ImageReader.setInput to connect the reader to the ImageInputStream.
Use ImageReader.read to get the BufferedImage.

How to write and read a string property in a Java Object from a file with FileChannel and ByteBuffer

Following is a sample class showing how I put String into ByteBuffer. I am able to write String to a file like this, but I am not sure how can I know the size of byte array to read the title back again when deserializing.
public class TestClass {
private Long id;
private String title;
public void write (final ByteBuffer byteBuffer) {
byteBuffer.putInt(title.length());
byteBuffer.put(title.getBytes());
}
public static UpdateFeed read (final ByteBuffer byteBuffer) {
final long id = byteBuffer.getLong();
final int titleLength = byteBuffer.getInt();
byte[] titleArr = new byte[titleLength];
byteBuffer.get(titleArr);
String title = new String(titleArr);
System.out.println("Title :"+title);
????????????????
return new TestClass(id,title);
}
}
I suggest you write the length first, then you can read back exactly that many bytes. You should always write you write method to write out what you need to read in your "read" method, in the same order and format.
Unless you have good reason to do so, its simpler to use DataInput/DataOutputStream which support writeUTF/readUTF.

Categories