Poniżej masz przykład ulepszonego skalowania, który wykorzystuje technikę podziału downscalingu na mniejsze kroki (każdy krok dzieli przez dwa). To skalowanie powstało na podstawie dostępnych w necie pomysłów i algorytmów. Zmniejszone detale takie jak tekst są zwykle widoczne jeżeli mają prawo być widoczne. :)
Kod można sobie wykorzystać lub przerobić.
package com.olamagato.swing;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ConvolveOp;
import java.awt.image.IndexColorModel;
import java.awt.image.Kernel;
import java.awt.image.WritableRaster;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* Klasa opakowująca BufferedImage mająca zmodyfikowane skalowanie
* @author Olamagato
* @version 2.1
*/
public class Image2D
{ //ustawienia dla skalowania
public enum Config { BEST_QUALITY, UNCHANGED_COLORS; }
/**
*
* @param width
* @param height
* @param imageType
*/
public Image2D(int width, int height, int imageType)
{
buf = new BufferedImage(width, height, imageType);
}
/**
*
* @param width
* @param height
* @param imageType
* @param cm
*/
public Image2D(int width, int height, int imageType, IndexColorModel cm)
{
buf = new BufferedImage(width, height, imageType, cm);
}
/**
*
* @param <K> typ klucza
* @param <V> typ wartości
* @param cm model koloru
* @param raster typ rastra
* @param isRasterPremultiplied flaga rastra
* @param properties ustawienia BufferedImage
*/
public <K,V> Image2D(ColorModel cm, WritableRaster raster,
boolean isRasterPremultiplied, Map<K,V> properties)
{
@SuppressWarnings("UseOfObsoleteCollectionType")
final java.util.Hashtable<K,V> ht =
new java.util.Hashtable<>(properties);
buf = new BufferedImage(cm, raster, isRasterPremultiplied, ht);
}
/**
* Opakowuje istniejący obiekt Buffered Image
* @param image opakowywany obraz
*/
public Image2D(BufferedImage image) { buf = image; }
/**
* Opakowuje istniejący obiekt przez skopiowanie jego obiektu obrazu
* @param image opakowywany obraz
*/
public Image2D(Image2D image) { buf = image.getBufferedImage(); }
/**
* Skaluje obraz do podanego wymiaru
* @param newSize rozmiar nowego obrazka
* @param typSkalowania rodzaj skalowania
* @return nowy wyskalowany do wymiaru obraz
*/
public Image2D scale(Dimension newSize, final Config typSkalowania)
{
if(typSkalowania == Config.BEST_QUALITY)
return scale(newSize.width, newSize.height,
RenderingHints.VALUE_INTERPOLATION_BICUBIC, true);
else if(typSkalowania == Config.UNCHANGED_COLORS)
{
Image2D roboczy = new Image2D(GUI.getDefGraph()
.createCompatibleImage(newSize.width, newSize.height,
Transparency.TRANSLUCENT));
Graphics2D g = roboczy.getBufferedImage().createGraphics();
g.setRenderingHints(niezmienne_kolory);
g.drawImage(roboczy.getBufferedImage(), 0, 0,
newSize.width, newSize.height, null);
g.dispose();
return roboczy;
}
return null;
}
/**
* Better up/down scaling.
*
* @param width new width
* @param height new height
* @param hint BufferedImage constans:
* 1. SCALE_DEFAULT, SCALE_FAST,
* 2. SCALE_SMOOTH,
* 3. SCALE_REPLICATE, SCALE_AREA_AVERAGING
*
* @return rescaled image
*/
public Image2D scale(final int width, final int height, final int hint)
{
Object h = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
Boolean q = false;
switch(hint)
{
case BufferedImage.SCALE_DEFAULT: case BufferedImage.SCALE_FAST:
break;
case BufferedImage.SCALE_SMOOTH:
q = true;
h = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
break;
case BufferedImage.SCALE_REPLICATE:
case BufferedImage.SCALE_AREA_AVERAGING:
h = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
}
return scale(width, height, h, q);
}
/**
* Use multi-step technique: start with original size, then
* scale down in multiple passes with drawImage()
* until the target size is reached
* @param width the desired width of the scaled instance,
* in pixels
* @param height the desired height of the scaled instance,
* in pixels
* @param interpolation one of the rendering hints that corresponds to
* {@code RenderingHints.KEY_INTERPOLATION} (e.g.
* {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR},
* {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR},
* {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC})
* @param highQuality if true, this method will use a multi-step
* scaling technique that provides higher quality than the usual
* one-step technique (only useful in downscaling cases, where
* {@code targetWidth} or {@code targetHeight} is
* smaller than the original dimensions, and generally only when
* the {@code BILINEAR} hint is specified)
* @return a scaled version of the original {@code Image2D}
*/
public Image2D scale(final int width, final int height,
Object interpolation, final boolean highQuality)
{
int typ = buf.getType();
if(typ == BufferedImage.TYPE_CUSTOM)
typ = (buf.getTransparency() == Transparency.OPAQUE)?
BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
//wielostopniowy downscaling
int w = buf.getWidth(), h = buf.getHeight();
//jednostopniowy upscaling lub gdy bez wysokiej jakości
if (width > w || !highQuality) w = width;
if (height > h || !highQuality) h = height;
BufferedImage roboczy, wynik = buf;
if(highQuality)
{ //wygładzenie przed zmianą rozmiaru
roboczy = new BufferedImage(w, h, typ);
roboczy.setAccelerationPriority(NO_ACCELERATION);
wygładzanie.filter(buf, roboczy);
wynik = roboczy;
}
do //pętla dla wielostopniowego skalowania
{ //dwukrotne zmniejszenie wymiarów w przypadku downscalingu
if(w > width && (w >>>= 1) < width ) //uboczne przypisania!
w = width;
if(h > height && (h >>>= 1) < height ) //uboczne przypisania!
h = height;
roboczy = new BufferedImage(w, h, typ);
roboczy.setAccelerationPriority(NO_ACCELERATION);
Graphics2D g2 = roboczy.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
interpolation);
g2.drawImage(wynik, 0, 0, w, h, null);
g2.dispose();
wynik = roboczy;
}
while(w != width || h != height);
return new Image2D(wynik);
}
private static final ConvolveOp wygładzanie =
new ConvolveOp(new Kernel(3, 3, wypełnij(new float[9], 1/9f)),
ConvolveOp.EDGE_NO_OP, null);
private static float[] wypełnij(float[] f, float liczba)
{ Arrays.fill(f, liczba); return f; }
private static final
RenderingHints niezmienne_kolory = niezmienne_kolory_init();
private static RenderingHints niezmienne_kolory_init()
{ //ustawić wszystkie potrzebne wartości RenderingHints
RenderingHints.Key[] k =
{
RenderingHints.KEY_INTERPOLATION,
RenderingHints.KEY_ANTIALIASING,
RenderingHints.KEY_COLOR_RENDERING,
RenderingHints.KEY_DITHERING,
RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.KEY_RENDERING,
RenderingHints.KEY_ALPHA_INTERPOLATION,
};
Object [] v =
{
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR,
RenderingHints.VALUE_ANTIALIAS_OFF,
RenderingHints.VALUE_COLOR_RENDER_SPEED,
RenderingHints.VALUE_DITHER_DISABLE,
RenderingHints.VALUE_STROKE_PURE,
RenderingHints.VALUE_RENDER_SPEED,
RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED,
};
HashMap<RenderingHints.Key,Object> mapa = new HashMap<>();
for(int i = 0; i < k.length; ++i)
mapa.put(k[i],v[i]);
return new RenderingHints(mapa);
}
/**
* @return Wrapped Image
*/
public BufferedImage getBufferedImage() { return buf; }
private static final float NO_ACCELERATION = 0f;
private BufferedImage buf;
}