Skip to content

Commit

Permalink
Optimize CompressedImage slightly, and construct from base64 PNG.
Browse files Browse the repository at this point in the history
  • Loading branch information
aadnk committed Dec 31, 2013
1 parent e0fed34 commit 3c5482f
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 25 deletions.
Expand Up @@ -16,6 +16,7 @@
import net.minecraft.util.io.netty.buffer.ByteBuf;
import net.minecraft.util.io.netty.buffer.Unpooled;
import net.minecraft.util.io.netty.handler.codec.base64.Base64;
import net.minecraft.util.io.netty.util.IllegalReferenceCountException;

import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.reflect.EquivalentConverter;
Expand Down Expand Up @@ -256,9 +257,18 @@ public WrappedServerPing deepClone() {
* Represents a compressed favicon.
* @author Kristian
*/
// Should not have been an inner class ... oh well.
public static class CompressedImage {
private final String mime;
private final byte[] data;
protected volatile String mime;
protected volatile byte[] data;
protected volatile String encoded;

/**
* Represents a compressed image with no content.
*/
protected CompressedImage() {
// Derived class should initialize some of the fields
}

/**
* Construct a new compressed image.
Expand Down Expand Up @@ -289,6 +299,20 @@ public static CompressedImage fromPng(byte[] data) {
return new CompressedImage("image/png", data);
}

/**
* Retrieve a compressed image from a base-64 encoded PNG file.
* @param base64 - the base 64-encoded PNG.
* @return The compressed image.
*/
public static CompressedImage fromBase64Png(String base64) {
try {
return new EncodedCompressedImage("data:image/png;base64," + base64);
} catch (IllegalArgumentException e) {
// Remind the caller
throw new IllegalReferenceCountException("Must be a pure base64 encoded string. Cannot be an encoded text.", e);
}
}

/**
* Retrieve a compressed image from an image.
* @param image - the image.
Expand All @@ -306,24 +330,7 @@ public static CompressedImage fromPng(RenderedImage image) throws IOException {
* @return The corresponding compressed image.
*/
public static CompressedImage fromEncodedText(String text) {
String mime = null;
byte[] data = null;

for (String segment : Splitter.on(";").split(text)) {
if (segment.startsWith("data:")) {
mime = segment.substring(5);
} else if (segment.startsWith("base64,")) {
byte[] encoded = segment.substring(7).getBytes(Charsets.UTF_8);
ByteBuf decoded = Base64.decode(Unpooled.wrappedBuffer(encoded));

// Read into a byte array
data = new byte[decoded.readableBytes()];
decoded.readBytes(data);
} else {
// We will ignore these segments
}
}
return new CompressedImage(mime, data);
return new EncodedCompressedImage(text);
}

/**
Expand All @@ -341,7 +348,15 @@ public String getMime() {
* @return The underlying compressed image.
*/
public byte[] getDataCopy() {
return data.clone();
return getData().clone();
}

/**
* Retrieve the underlying data, with no copying.
* @return The underlying data.
*/
protected byte[] getData() {
return data;
}

/**
Expand All @@ -350,15 +365,79 @@ public byte[] getDataCopy() {
* @throws IOException If the image data could not be decoded.
*/
public BufferedImage getImage() throws IOException {
return ImageIO.read(new ByteArrayInputStream(data));
return ImageIO.read(new ByteArrayInputStream(getData()));
}

/**
* Convert the compressed image to encoded text.
* @return The encoded text.
*/
public String toEncodedText() {
return "data:" + mime + ";base64," + Base64.encode(Unpooled.wrappedBuffer(data)).toString(Charsets.UTF_8);
if (encoded == null) {
final ByteBuf buffer = Unpooled.wrappedBuffer(getData());
String computed = "data:" + mime + ";base64," +
Base64.encode(buffer).toString(Charsets.UTF_8);

encoded = computed;
}
return encoded;
}
}

/**
* Represents a compressed image that starts out as an encoded base 64 string.
* @author Kristian
*/
private static class EncodedCompressedImage extends CompressedImage {
public EncodedCompressedImage(String encoded) {
this.encoded = encoded;
}

/**
* Ensure that we have decoded the content of the encoded text.
*/
protected void initialize() {
if (mime == null || data == null) {
decode();
}
}

/**
* Decode the encoded text.
*/
protected void decode() {
for (String segment : Splitter.on(";").split(encoded)) {
if (segment.startsWith("data:")) {
this.mime = segment.substring(5);
} else if (segment.startsWith("base64,")) {
byte[] encoded = segment.substring(7).getBytes(Charsets.UTF_8);
ByteBuf decoded = Base64.decode(Unpooled.wrappedBuffer(encoded));

// Read into a byte array
byte[] data = new byte[decoded.readableBytes()];
decoded.readBytes(data);
this.data = data;
} else {
// We will ignore these segments
}
}
}

@Override
protected byte[] getData() {
initialize();
return super.getData();
}

@Override
public String getMime() {
initialize();
return super.getMime();
}

@Override
public String toEncodedText() {
return encoded;
}
}
}
Expand Up @@ -6,6 +6,7 @@

import org.junit.BeforeClass;
import org.junit.Test;
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;

import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.wrappers.WrappedServerPing.CompressedImage;
Expand Down Expand Up @@ -35,7 +36,10 @@ public void test() throws IOException {
assertEquals("Minecraft 123", serverPing.getVersionName());
assertEquals(4, serverPing.getVersionProtocol());

assertArrayEquals(original, serverPing.getFavicon().getDataCopy());
assertArrayEquals(original, serverPing.getFavicon().getData());

CompressedImage copy = CompressedImage.fromBase64Png(Base64Coder.encodeLines(tux.getData()));
assertArrayEquals(copy.getData(), serverPing.getFavicon().getData());
}

}

0 comments on commit 3c5482f

Please sign in to comment.