base 64 encoding/decoding 后图像文件大小不同
image file size is different after base 64 encoding/decoding
我正在使用以下测试读取文件,对其进行 base 64 编码,然后将 base 64 解码回新图像。我注意到新的图像文件大小(转换后)明显小于原始图像,这让我觉得不知何故,部分图像数据在转换过程中丢失了。我可以看到图像,但担心图像质量。任何关于我可能做错了什么的见解将不胜感激。
测试class:
package test;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
public class ImageTest { //should be B64Test
public static void main(String[] args) {
ImageTest imageTest = new ImageTest();
try {
BufferedImage img = null;
BufferedImage finalImg = null;
try {
// img = ImageIO.read(new File("/home/user/Desktop/test1.jpg"));
img = ImageIO.read(Files.newInputStream(Paths.get("/home/user/Desktop/test1.jpg")));
//encode base64 and print
final String base64encoded = ImageConverter.encodeToString(img, "jpeg");
System.out.println("read file " + base64encoded);
//convert base64 string to image
finalImg = ImageConverter.decodeToImage(b64encoded);
ImageIO.write(finalImg, "jpeg", new File("/home/user/Desktop/test2.jpg"));
} catch (IOException e) {
System.out.println("exception " + e);
}
} catch (Exception e) {
System.out.println("exception " + e);
}
}
}
ImageConverter
package test;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import sun.misc.BASE64Encoder;
import sun.misc.BASE64Decoder;
public class ImageConverter {
public static String imgToBase64String(final RenderedImage img, final String formatName) {
final ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
ImageIO.write(img, formatName, Base64.getEncoder().wrap(os));
return os.toString(StandardCharsets.ISO_8859_1.name());
} catch (final IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
public static BufferedImage base64StringToImg(final String base64String) {
try {
return ImageIO.read(new ByteArrayInputStream(Base64.getDecoder().decode(base64String)));
} catch (final IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
public static String encodeToString(BufferedImage image, String type) {
String imageString = null;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
ImageIO.write(image, type, bos);
byte[] imageBytes = bos.toByteArray();
BASE64Encoder encoder = new BASE64Encoder();
imageString = encoder.encode(imageBytes);
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
return imageString;
}
public static BufferedImage decodeToImage(String imageString) {
BufferedImage image = null;
byte[] imageByte;
try {
BASE64Decoder decoder = new BASE64Decoder();
imageByte = decoder.decodeBuffer(imageString);
ByteArrayInputStream bis = new ByteArrayInputStream(imageByte);
image = ImageIO.read(bis);
bis.close();
} catch (Exception e) {
e.printStackTrace();
}
return image;
}
}
我可以尝试测试 jdk8 中可用的 base 64 encoder/decoder 以及 sun.java.misc 中的(我意识到我不需要使用)。关于可能导致图像尺寸缩小的任何想法(如果需要,我更愿意自己做,使用 imagemagick 或 graphicsmagick 等)。
原始图像为 1.2 MB(1,249,934 字节),但新图像为 354.5 kB(354,541 字节)- width/height 两幅图像相同。
正如@JBNizet 在他的评论中指出的那样,大小变化的原因(大小也可能增加,具体取决于输入图像和压缩设置),是因为您不只是 encoding/decoding二进制数据 to/from Base64,您还使用 JPEG 编码(使用默认编码设置)重新编码 图像数据(两次)。除非原始图像使用完全相同的设置进行编码,否则您将失去一些精度,并且文件大小将发生变化。
文件大小减小的另一个可能原因是 BufferedImage
不包含原始 JPEG 文件中包含的任何元数据。因此,您对 JPEG 重新编码的过程也会丢失任何 Exif 或 XMP 元数据、缩略图、颜色配置文件等。根据图像的来源,这可能会占文件大小的很大一部分。
同样,正如@JBNizet 所说,最好的办法是在这种情况下完全不涉及 ImageIO
,只需使用普通文件 I/O 并使用 Base64 对原始字节进行编码,然后再次解码准确恢复原始文件内容。
PS:如果你打算对Base64 encoding/decoding之间的图像进行图像处理,你当然需要解码图像数据(使用ImageIO
或类似的),但您应该尝试只执行一次(以获得更好的性能),并且也许考虑保留元数据。此外,我认为图像 encoding/decoding 和 Base64 encoding/decoding 是不同的问题,不应像现在这样交错。将其拆分,以获得 better separation of concerns。
我正在使用以下测试读取文件,对其进行 base 64 编码,然后将 base 64 解码回新图像。我注意到新的图像文件大小(转换后)明显小于原始图像,这让我觉得不知何故,部分图像数据在转换过程中丢失了。我可以看到图像,但担心图像质量。任何关于我可能做错了什么的见解将不胜感激。
测试class:
package test;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
public class ImageTest { //should be B64Test
public static void main(String[] args) {
ImageTest imageTest = new ImageTest();
try {
BufferedImage img = null;
BufferedImage finalImg = null;
try {
// img = ImageIO.read(new File("/home/user/Desktop/test1.jpg"));
img = ImageIO.read(Files.newInputStream(Paths.get("/home/user/Desktop/test1.jpg")));
//encode base64 and print
final String base64encoded = ImageConverter.encodeToString(img, "jpeg");
System.out.println("read file " + base64encoded);
//convert base64 string to image
finalImg = ImageConverter.decodeToImage(b64encoded);
ImageIO.write(finalImg, "jpeg", new File("/home/user/Desktop/test2.jpg"));
} catch (IOException e) {
System.out.println("exception " + e);
}
} catch (Exception e) {
System.out.println("exception " + e);
}
}
}
ImageConverter
package test;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import sun.misc.BASE64Encoder;
import sun.misc.BASE64Decoder;
public class ImageConverter {
public static String imgToBase64String(final RenderedImage img, final String formatName) {
final ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
ImageIO.write(img, formatName, Base64.getEncoder().wrap(os));
return os.toString(StandardCharsets.ISO_8859_1.name());
} catch (final IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
public static BufferedImage base64StringToImg(final String base64String) {
try {
return ImageIO.read(new ByteArrayInputStream(Base64.getDecoder().decode(base64String)));
} catch (final IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
public static String encodeToString(BufferedImage image, String type) {
String imageString = null;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
ImageIO.write(image, type, bos);
byte[] imageBytes = bos.toByteArray();
BASE64Encoder encoder = new BASE64Encoder();
imageString = encoder.encode(imageBytes);
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
return imageString;
}
public static BufferedImage decodeToImage(String imageString) {
BufferedImage image = null;
byte[] imageByte;
try {
BASE64Decoder decoder = new BASE64Decoder();
imageByte = decoder.decodeBuffer(imageString);
ByteArrayInputStream bis = new ByteArrayInputStream(imageByte);
image = ImageIO.read(bis);
bis.close();
} catch (Exception e) {
e.printStackTrace();
}
return image;
}
}
我可以尝试测试 jdk8 中可用的 base 64 encoder/decoder 以及 sun.java.misc 中的(我意识到我不需要使用)。关于可能导致图像尺寸缩小的任何想法(如果需要,我更愿意自己做,使用 imagemagick 或 graphicsmagick 等)。
原始图像为 1.2 MB(1,249,934 字节),但新图像为 354.5 kB(354,541 字节)- width/height 两幅图像相同。
正如@JBNizet 在他的评论中指出的那样,大小变化的原因(大小也可能增加,具体取决于输入图像和压缩设置),是因为您不只是 encoding/decoding二进制数据 to/from Base64,您还使用 JPEG 编码(使用默认编码设置)重新编码 图像数据(两次)。除非原始图像使用完全相同的设置进行编码,否则您将失去一些精度,并且文件大小将发生变化。
文件大小减小的另一个可能原因是 BufferedImage
不包含原始 JPEG 文件中包含的任何元数据。因此,您对 JPEG 重新编码的过程也会丢失任何 Exif 或 XMP 元数据、缩略图、颜色配置文件等。根据图像的来源,这可能会占文件大小的很大一部分。
同样,正如@JBNizet 所说,最好的办法是在这种情况下完全不涉及 ImageIO
,只需使用普通文件 I/O 并使用 Base64 对原始字节进行编码,然后再次解码准确恢复原始文件内容。
PS:如果你打算对Base64 encoding/decoding之间的图像进行图像处理,你当然需要解码图像数据(使用ImageIO
或类似的),但您应该尝试只执行一次(以获得更好的性能),并且也许考虑保留元数据。此外,我认为图像 encoding/decoding 和 Base64 encoding/decoding 是不同的问题,不应像现在这样交错。将其拆分,以获得 better separation of concerns。