Ebben a témában rengeteg bejegyzés található a neten, én személy szerint mégsem találtam elfogadható megoldás, ami egyben megoldaná minden problémámat, kivéve a
Java Advanced Imaging-ot. Utóbbival elég nagy problémám volt, hogy nincsen agyondokumentálva, vannak ugyan példák, de azokon eligazodni is felér egy rém-álommal. A rossz tapasztalatok sarkalltak arra, hogy belekezdjek egy, az én igényeimet kielégítő, eszköz megírásába, mely az alapvető webes környezetben előforduló problémákat orvosolja. A projekt elérkezett az első "mérföld-kőhöz", így gondoltam szánok rá pár sort.
Jelenlegi stádiumban képes átméretezni jpg, png, gif, anim gif formátumokat, alkalmazza az élsimítás technológiát, viszont a transzparens képekbe még beletörik a foga. A művelet pilléreit szeretném itt bemutatni, a teljes forráskódot elérhetővé teszem, de mivel felhasználtam pár osztályt, amit letöltöttem és javítottam, ezek pontos liszenszelését még egyeztetem.
Első esetben nézzük az egyszerűbb oldalát a dolognak, amikor nem animált gif-et kell átméretezni. Mivel a legtöbb példa csak a
Graphics2D beépített élsimítását használta fel, aminek minősége finoman szólva is vacak, így átméretezés előtt egy blur effektet húzok a képre. A példa csak a kicsinyítés problémájával foglalkozik, mivel a jelentős minőség-romlás ilyen esetben érzékelhető igazán, és a gyakorlatban is ritkán kell nagyítani egy felhasználó által feltöltött képet.
OutputStream out;
BufferedImage bufferedImage;
String extension;
//...
if (!extension.equals("gif")) {
//Első lépésként, amenniyben jpg-a forrás kép, át kell állítanunk a típusát, mivel az alapértelmezett típus nem támogatja a blur készítését, minden más esetben, kompatibilissé tesszük a képet, szintén a blur-ozhatóság érdekében.
if (mimeType.equals("image/jpeg") || mimeType.equals("image/pjpeg")) {
bufferedImage = convert(bufferedImage, BufferedImage.TYPE_INT_RGB);
} else {
bufferedImage = createCompatibleImage(bufferedImage);
}
//Elvégezzük a blur-ozást, és átméretezést, majd mehet a kép a kimenetre.
ImageIO.write(resize(blur(bufferedImage), newWidth.intValue(), newHeight.intValue()), extension, out);
out.flush();
out.close();
}
A meghívott metódusok:
//Átméretezi a képet.
private BufferedImage resize(BufferedImage image, int width, int height) {
BufferedImage result = new BufferedImage(width, height, image.getType() == 0 ? BufferedImage.TYPE_INT_ARGB : image.getType());
Graphics2D g = result.createGraphics();
g.setComposite(AlphaComposite.Src);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.drawImage(image, 0, 0, width, height, null);
g.dispose();
return result;
}
//Blur-ozza a képet.
private BufferedImage blur(BufferedImage image) {
float ninth = 1.0f / 9.0f;
float[] blurKernel = {
ninth, ninth, ninth,
ninth, ninth, ninth,
ninth, ninth, ninth
};
Map map = new HashMap();
map.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
map.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
map.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
BufferedImageOp op = new ConvolveOp(new Kernel(3, 3, blurKernel), ConvolveOp.EDGE_NO_OP, new RenderingHints(map));
return op.filter(image, null);
}
//Átalakítja a típusát a képnek.
private BufferedImage convert(BufferedImage image, int type) {
BufferedImage result = new BufferedImage(image.getWidth(), image.getHeight(), type);
Graphics2D g = result.createGraphics();
g.drawRenderedImage(image, null);
g.dispose();
return result;
}
//Kompatibilissé teszi a képet.
private static BufferedImage createCompatibleImage(BufferedImage image) {
GraphicsConfiguration gc = BufferedImageGraphicsConfig.getConfig(image);
BufferedImage result = gc.createCompatibleImage(image.getWidth(), image.getHeight(), Transparency.BITMASK);
Graphics2D g = result.createGraphics();
g.drawRenderedImage(image, null);
g.dispose();
return result;
}
A nehezebb eset, mint említettem, animált gifek átméretezése, mivel a Graphics2D nem támogatja alapértelmezetten azokat. A megoldás nem egyedüli érdemem, bár a letöltött kódokat javítanom kellett. A lényeg, hogy a gif kép-kockáin egyesével végig kell iterálni, és az előbb említett műveletek végrehajtása után, a cél képbe beletenni az immár átméretezett példányokat.
try {
GifDecoder d = new GifDecoder();
d.read(image.getAbsolutePath());
AnimatedGifEncoder e = new AnimatedGifEncoder();
e.setRepeat(d.getLoopCount());
e.start(out);
int type = bufferedImage.getType() == 0 ? BufferedImage.TYPE_INT_ARGB : bufferedImage.getType();
for (int i = 0; i < d.getFrameCount(); i++) {
e.setDelay(d.getDelay(i));
BufferedImage frameBuffer = new BufferedImage(origWidth, origHeight, BufferedImage.TYPE_BYTE_INDEXED);
frameBuffer.getGraphics().drawImage(d.getFrame(i), 0, 0, null);
if (mimeType.equals("image/jpeg") || mimeType.equals("image/pjpeg")) {
frameBuffer = convert(frameBuffer, BufferedImage.TYPE_INT_RGB);
} else {
frameBuffer = createCompatibleImage(frameBuffer);
}
e.addFrame(resize(blur(frameBuffer), newWidth.intValue(), newHeight.intValue()));
}
e.finish();
out.flush();
out.close();
} catch (Exception e) {}
A javított osztályokat egyenlőre
pastebin.com-ra töltöttem fel.
Nem állítom, hogy ez a legjobb és legszebb megoldás, és azt sem, hogy tökéletes eredményt hoz minden típusú képnél, csak azt tudom, hogy a transzparens képek kivételével eddig bevált.