Questo può essere risolto con un RadialGradientPaint
e l'appropriato AlphaComposite
.
Quello che segue è un MCVE che mostra come questo può essere fatto. Usa le stesse immagini di user1803551 used in his answer, quindi uno screenshot sembrerebbe (quasi) lo stesso.Ma questo aggiunge un MouseMotionListener
che consente di spostare il foro intorno, facendo passare la posizione corrente del mouse al metodo updateGradientAt
, dove la creazione effettiva dell'immagine desiderata avviene:
- Si riempie prima l'immagine con la immagine originale
- Quindi crea un
RadialGradientPaint
, che ha un colore completamente opaco al centro e un colore completamente trasparente al bordo (!). Questo può sembrare controintuitivo, ma l'intenzione è di "ritagliare" il buco da un'immagine esistente, che viene eseguita con il passaggio successivo:
Uno AlphaComposite.DstOut
è assegnato allo Graphics2D
. Questo causa un "inversione" dei valori alfa, come nella formula
Ar = Ad*(1-As)
Cr = Cd*(1-As)
dove r
sta per "risultato", s
sta per "source" e d
sta per "destinazione"
Il risultato è un'immagine con la trasparenza del gradiente radiale nella posizione desiderata, completamente trasparente al centro e completamente opaca al bordo (!). Questa combinazione di Paint
e Composite
viene quindi utilizzata per riempire un ovale con le dimensioni e le coordinate del foro. (Si potrebbe anche fare una chiamata fillRect
, riempiendo l'intera immagine - non cambierebbe il risultato).
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RadialGradientPaint;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class TransparentGradientInImage
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
TransparentGradientInImagePanel p =
new TransparentGradientInImagePanel();
f.getContentPane().add(p);
f.setSize(800, 600);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class TransparentGradientInImagePanel extends JPanel
{
private BufferedImage background;
private BufferedImage originalImage;
private BufferedImage imageWithGradient;
TransparentGradientInImagePanel()
{
try
{
background = ImageIO.read(
new File("night-sky-astrophotography-1.jpg"));
originalImage = convertToARGB(ImageIO.read(new File("7bI1Y.jpg")));
imageWithGradient = convertToARGB(originalImage);
}
catch (IOException e)
{
e.printStackTrace();
}
addMouseMotionListener(new MouseAdapter()
{
@Override
public void mouseMoved(MouseEvent e)
{
updateGradientAt(e.getPoint());
}
});
}
private void updateGradientAt(Point point)
{
Graphics2D g = imageWithGradient.createGraphics();
g.drawImage(originalImage, 0, 0, null);
int radius = 100;
float fractions[] = { 0.0f, 1.0f };
Color colors[] = { new Color(0,0,0,255), new Color(0,0,0,0) };
RadialGradientPaint paint =
new RadialGradientPaint(point, radius, fractions, colors);
g.setPaint(paint);
g.setComposite(AlphaComposite.DstOut);
g.fillOval(point.x - radius, point.y - radius, radius * 2, radius * 2);
g.dispose();
repaint();
}
private static BufferedImage convertToARGB(BufferedImage image)
{
BufferedImage newImage =
new BufferedImage(image.getWidth(), image.getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = newImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return newImage;
}
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(background, 0, 0, null);
g.drawImage(imageWithGradient, 0, 0, null);
}
}
Si può giocare con il fractions
e colors
del RadialGradientPaint
per ottenere effetti diversi. Ad esempio, questi valori ...
float fractions[] = { 0.0f, 0.1f, 1.0f };
Color colors[] = {
new Color(0,0,0,255),
new Color(0,0,0,255),
new Color(0,0,0,0)
};
causa un piccolo foro trasparente, con un grande, morbido "corona":
che tali valori
float fractions[] = { 0.0f, 0.9f, 1.0f };
Color colors[] = {
new Color(0,0,0,255),
new Color(0,0,0,255),
new Color(0,0,0,0)
};
causare un centro grande, nitidamente trasparente, con una piccola "corona":
Gli RadialGradientPaint
JavaDocs hanno alcuni esempi che possono aiutare a trovare i valori desiderati.
alcune domande relative dove ho postato (simili) risponde:
EDIT In risposta alla domanda circa le prestazioni che è stato chiesto in comme nti
La questione di come le prestazioni dell'approccio Paint
/Composite
paragona l'approccio getRGB
/setRGB
è davvero interessante. Dalla mia esperienza precedente, il mio istinto sarebbe stato che il primo fosse molto più veloce del secondo, perché, in generale, getRGB
/setRGB
tende a essere lento, e i meccanismi integrati sono altamente ottimizzati (e, in alcuni casi , potrebbe persino essere accelerato dall'hardware).
In realtà, l'approccio Paint
/Composite
è più veloce rispetto al metodo getRGB
/setRGB
, ma non tanto quanto mi aspettavo. Quanto segue è, naturalmente, non è una realtà profonda "benchmark" (non ho impiegano Pinza o JMH per questo), ma dovrebbe dare una buona stima circa l'effettiva performance:
// NOTE: This is not really a sophisticated "Benchmark",
// but gives a rough estimate about the performance
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RadialGradientPaint;
import java.awt.image.BufferedImage;
public class TransparentGradientInImagePerformance
{
public static void main(String[] args)
{
int w = 1000;
int h = 1000;
BufferedImage image0 = new BufferedImage(w, h,
BufferedImage.TYPE_INT_ARGB);
BufferedImage image1 = new BufferedImage(w, h,
BufferedImage.TYPE_INT_ARGB);
long before = 0;
long after = 0;
int runs = 100;
for (int radius = 100; radius <=400; radius += 10)
{
before = System.nanoTime();
for (int i=0; i<runs; i++)
{
transparitize(image0, w/2, h/2, radius);
}
after = System.nanoTime();
System.out.println(
"Radius "+radius+" with getRGB/setRGB: "+(after-before)/1e6);
before = System.nanoTime();
for (int i=0; i<runs; i++)
{
updateGradientAt(image0, image1, new Point(w/2, h/2), radius);
}
after = System.nanoTime();
System.out.println(
"Radius "+radius+" with paint "+(after-before)/1e6);
}
}
private static void transparitize(
BufferedImage imgA, int centerX, int centerY, int r)
{
for (int x = centerX - r; x < centerX + r; x++)
{
for (int y = centerY - r; y < centerY + r; y++)
{
double distance = Math.sqrt(
Math.pow(Math.abs(centerX - x), 2) +
Math.pow(Math.abs(centerY - y), 2));
if (distance > r)
continue;
int argb = imgA.getRGB(x, y);
int a = (argb >> 24) & 255;
double factor = distance/r;
argb = (argb - (a << 24) + ((int) (a * factor) << 24));
imgA.setRGB(x, y, argb);
}
}
}
private static void updateGradientAt(BufferedImage originalImage,
BufferedImage imageWithGradient, Point point, int radius)
{
Graphics2D g = imageWithGradient.createGraphics();
g.drawImage(originalImage, 0, 0, null);
float fractions[] = { 0.0f, 1.0f };
Color colors[] = { new Color(0, 0, 0, 255), new Color(0, 0, 0, 0) };
RadialGradientPaint paint = new RadialGradientPaint(point, radius,
fractions, colors);
g.setPaint(paint);
g.setComposite(AlphaComposite.DstOut);
g.fillOval(point.x - radius, point.y - radius, radius * 2, radius * 2);
g.dispose();
}
}
I tempi sul mio PC si trovano lungo le linee di
...
Radius 390 with getRGB/setRGB: 1518.224404
Radius 390 with paint 764.11017
Radius 400 with getRGB/setRGB: 1612.854049
Radius 400 with paint 794.695199
dimostrano che il metodo Paint
/Composite
è approssimativamente due volte più veloce del metodo getRGB
/setRGB
. Oltre alle prestazioni, lo Paint
/Composite
presenta altri vantaggi, principalmente le possibili parametrizzazioni dello RadialGradientPaint
descritte sopra, che sono motivi per cui preferirei questa soluzione.
Molto bello, questo offre prestazioni migliori della mia soluzione? – user1803551
@ user1803551 Sembra dare prestazioni migliori (ha aggiunto un EDIT per questo), ma non tanto quanto mi aspettavo, ammettiamolo. (Ciò potrebbe essere dovuto alla complessità/flessibilità di ' RadialGradientPaint' - con questo potresti ottenere alcuni effetti eleganti e semplicemente "tagliare un buco" è uno dei casi più semplici possibili per questa classe) – Marco13