2016-01-09 33 views
5

Graphics2dを使用して、背景画像の上にBufferedImageを描画しようとしています。この画像の任意の点で、描かれた画像に「円形の穴を切って」、背景が透けて見えるようにしたいと思います。Java Graphics2D - グラデーションの不透明度を持つ画像を描画する

私は、穴が固体の形ではなくむしろ勾配であることを望みます。つまり、BufferedImageのすべてのピクセルは、穴の中心からの距離に比例するアルファ/不透明度を持つ必要があります。

私はGraphics2dグラジエントとAlphaCompositeと幾分慣れていますが、これらを組み合わせる方法はありますか?

この効果を達成するための(非常に高価ではない)方法はありますか?

答えて

6

これはRadialGradientPaint、適切なAlphaCompositeで解決することができます。

以下は、これを行う方法を示すMCVEです。それはuser1803551 used in his answerと同じイメージを使用するので、スクリーンショットはほぼ同じに見えます。

  • それは最初にイメージを塗りつぶし:しかし、この1を使用すると、所望の画像の実際の作成が行わupdateGradientAt方法、現在のマウスの位置を渡すことによって、周りに穴を移動することができますMouseMotionListenerを追加します元の画像
  • 次に、RadialGradientPaintを作成します。中央には完全に不透明な色、境界には完全に透明な色(!)があります。これは直観に反して見えるかもしれませんが、次のステップで行われる既存のイメージから穴を「切り取る」ことを意図しています。
  • Graphics2Dに割り当てられています。これは、式

    Ar = Ad*(1-As) 
    Cr = Cd*(1-As) 
    

    「ソース」を意味s「結果」のrスタンド、および「宛先」

ため dスタンドのように、アルファ値の「反転」させます

結果は、希望の位置に放射状のグラデーション透明度を持ち、中央が完全に透明で、境界線(!)が完全に不透明なイメージです。次に、PaintCompositeのこの組み合わせを使用して、楕円を穴のサイズと座標で充填します。 (1つはfillRectコールを行い、イメージ全体を満たしてもよい - 結果は変わらない)。

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); 
    } 
} 

あなたは、異なる効果を達成するためにRadialGradientPaintfractionscolorsでプレイしてもよいです。例えば、大規模な、ソフトな "コロナ" で、これらの値...

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) 
}; 

原因小さな、透明の穴、:これらの値に対し

TransparentGradientInImage02.png

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) 
}; 

小さな「コロナ」を有する大きくて鮮明な透明な中心をもたらす:

TransparentGradientInImage01.png

RadialGradientPaint JavaDocsには、望ましい値を見つけるのに役立ついくつかの例があります。


私は(同様の)答え掲載いくつかの関連質問:そのパフォーマンスについての質問を受けて


EDITをその仲間で尋ねられたNTS

Paint/CompositeアプローチのパフォーマンスがgetRGB/setRGBアプローチとの比較についての質問は確かに面白いです。私の以前の経験から、最初のものは2番目のものよりもはるかに高速だったでしょう。一般に、getRGB/setRGBは遅くなりがちで、組み込みのメカニズムは高度に最適化されています(場合によってはハードウェアアクセラレーションさえも可能です)。実際に

Paint/CompositeアプローチgetRGB/setRGBアプローチよりも早く、私は期待と同じくらいではありません。私のPC上のタイミングが一緒にいる

// 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(); 
    } 
} 

:以下は、当然のことながら、本当に深い「ベンチマーク」(私はこれのためにキャリパーやJMHを採用していなかった)、実際のパフォーマンスについて良い評価を与えるべきではありませんPaint/Composite方法は約2倍の速さgetRGB/setRGB方法としてあることを示す

... 
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 

の行。パフォーマンスとは別に、Paint/Compositeには、上記のRadialGradientPaintの可能なパラメータ化を中心とするいくつかの他の利点があります。これが私がこのソリューションを好む理由です。

+0

とてもいいです、 – user1803551

+1

@ user1803551これはEDITを追加したほうがパフォーマンスが良いようですが、期待したほどのパフォーマンスは得られていません(これは ' RadialGradientPaint' - これでいくつかの素晴らしい効果を得ることができ、単純に「穴をカットする」はこのクラスにとって最も簡単なケースの1つです) – Marco13

2

この透明な「穴」を動的に作成するのか、それとも1回限りのものなのかわかりません。あなたが望むものを達成するためのいくつかの方法があると確信しています。ピクセルを直接変更してそのうちの1つを表示しています。は最高のパフォーマンスワイズではないかもしれません。(私はちょうど他の方法私はそれがあなたが正確に何をするかによって決まると思う)。ここで私はオーストラリアのオゾン層に穴を描く

enter image description here

public class Paint extends JPanel { 

    BufferedImage imgA; 
    BufferedImage bck; 

    Paint() { 

     BufferedImage img = null; 
     try { 
      img = ImageIO.read(getClass().getResource("img.jpg")); // images linked below 
      bck = ImageIO.read(getClass().getResource("bck.jpg")); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
     imgA = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB); 
     Graphics2D g2d = imgA.createGraphics(); 
     g2d.drawImage(img, 0, 0, null); 
     g2d.dispose(); 

     transparitize(200, 100, 80); 
    } 

    private void transparitize(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); 
      } 
     } 
    } 

    @Override 
    protected void paintComponent(Graphics g) { 

     super.paintComponent(g); 
     g.drawImage(bck, 0, 0, null); 
     g.drawImage(imgA, 0, 0, null); 
    } 

    @Override 
    public Dimension getPreferredSize() { 

     return new Dimension(bck.getWidth(), bck.getHeight()); // because bck is larger than imgA, otherwise use Math.max 
    } 
} 

アイデアは、アルファ(または何か他)を変更、getRGBとピクセルのARGB値を取得することであり、 setRGBと設定します。私は放射状の勾配を中心と半径にする方法を作りました。それは確かに改善することができます、私はそれをあなたに任せます(ヒント:centerX - rは範囲外になる可能性があります; distance > rのピクセルは、すべて繰り返しから削除できます)。

注:

  • 私は、背景画像を塗装し、その上にオーバー画像を小さくするだけで、背景にはどのようなものかを明確に示すこと。
  • intのアルファ値を読み取って変更するには、かなりの方法があります。このサイトを検索すると、少なくとも2〜3つ以上の方法が見つかります。
  • お気に入りのトップレベルコンテナに追加して実行します。

源:

+0

もう1つの解決策が見つからない場合、これは多かれ少なかれ私が計画していたことです。私はそれを行うための組み込みの、最適化された関数があるかどうか疑問に思っていました。 – B1CL0PS

+1

いくつかの境界チェックも必要です。メソッドがサイズ(100,100)の画像上のパラメータ(99,99,10)で呼び出された場合。 – Marco13

+0

@ Marco13メソッドについて話すときに境界チェックを述べました(「*確かに改善することができます、私はあなたにそれを残します(ヒント: 'centerX-r'は範囲外になる可能性があります*)」 – user1803551

関連する問題