Swing 投影邊框

很久之前發在百度Java吧,很快被洪水淹沒……


http://tieba.baidu.com/p/1977309130



最近想起來要用,稍微整理了一下發在這裏算個存檔吧。


代碼中用到了 JHLab,只用其中的高斯模糊,可以在maven的repository找到: 

http://mvnrepository.com/artifact/com.jhlabs/filters


先上用法示例和截圖:




import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.util.Arrays;

public class ShadowBorderTest extends JPanel {

  public static void main(String[] args) {

    SwingUtilities.invokeLater(new Runnable() {

      @Override
      public void run() {

        JFrame frame = new JFrame("Test ShadowBorder");

        frame.setContentPane(new ShadowBorderTest());

        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
      }
    });
  }

  ShadowBorderTest() {

    super(new GridLayout(2, 2, 20, 20));

    super.setBackground(Color.GRAY.brighter());

    JPanel p1 = new JPanel();
    p1.setBorder(BorderFactory.createCompoundBorder(

            ShadowBorder.newInstance(),
            BorderFactory.createLineBorder(Color.WHITE)
    ));

    JPanel p2 = new JPanel();
    p2.setBorder(BorderFactory.createCompoundBorder(

            ShadowBorder.newBuilder().shadowSize(3).center().build(),
            BorderFactory.createLineBorder(Color.WHITE)
    ));

    JPanel p3 = new JPanel();
    p3.setBorder(BorderFactory.createCompoundBorder(

            ShadowBorder.newBuilder().shadowColor(Color.ORANGE).build(),
            BorderFactory.createLineBorder(Color.WHITE)
    ));

    JPanel p4 = new JPanel();
    p4.setBorder(BorderFactory.createCompoundBorder(

            ShadowBorder.newBuilder().shadowAlpha(0.7f).top().build(),
            BorderFactory.createLineBorder(Color.WHITE)
    ));

    Dimension d = new Dimension(150, 150);
    for (JPanel p : Arrays.asList(p1, p2, p3, p4)) {

      p.setPreferredSize(d);
      add(p);
    }

    setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
  }
}

然後是 ShadowBorder 類的代碼:


/* 
 * Copyright 2012 wuyou ([email protected]) 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */

import com.jhlabs.image.GaussianFilter;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;

/**
 * ShadowBorder is designed to be immutable, so that it can safely cache
 * the "shadow fragments" images, and reuse them for efficiency.
 * 
 * <p/>
 * The shadow fragments images are determined by the size, color, and alpha
 * of the shadow.
 * 
 * <p/>
 * Instance can be retrieved via an overloading {@code newInstance()} method
 * or a {@code ShadowBorder.ObjectFactory} instance, using a "builder design pattern".
 * 
 * <p/>
 * It is recommended that the component to apply this border is NOT opaque, 
 * because "shadow border" implies a somewhat "transparent" effect. In the case 
 * of being applied to an opaque component, this class tries to use an expensive 
 * reflection hack to "erase" the border area before its painting, which is not 
 * fully tested and can be dangerous.
 * 
 * <p/>
 * This class uses a third-party graphics library to draw the "gaussian blur" 
 * effect.
 * 
 * <p/>
 * The dependency jar can be downloaded from the JHLabs website, or from 
 * the maven central repository with the following POM coordination:
 * 
 * <p/>
 * <pre>{@code 
 * 
 * <groupId>com.jhlabs</groupId>
 * <artifactId>filters</artifactId>
 * <packaging>jar</packaging>
 * <version>2.0.235</version>
 * <name>JHLabs Image Processing Filters</name>
 * <description>A collection of image processing filters.</description>
 * <url>http://www.jhlabs.com/ip/index.html</url>
 * 
 * }</pre>
 * 
 * @author raistlic
 * @date   Nov 10, 2012
 */

public class ShadowBorder implements Border {
  
  public static final int DEFAULT_SIZE = 4;
  public static final Color DEFAULT_COLOR = Color.BLACK;
  public static final float DEFAULT_ALPHA = 0.5f;
  
  private static final float DEFAULT_PADDING_TOP = 0.0f;
  private static final float DEFAULT_PADDING_LEFT = 0.0f;
  
  public static ShadowBorder newInstance() {
    
    return newInstance(DEFAULT_SIZE, DEFAULT_COLOR, DEFAULT_ALPHA);
  }
  
  public static ShadowBorder newInstance(int size) {
    
    return newInstance(size, DEFAULT_COLOR, DEFAULT_ALPHA);
  }
  
  public static ShadowBorder newInstance(int size, Color c) {
    
    return newInstance(size, c, DEFAULT_ALPHA);
  }
  
  public static ShadowBorder newInstance(int size, Color c, float alpha) {
    
    size = Math.max(size, 0);
    alpha = Math.max(alpha, 0f);
    alpha = Math.min(alpha, 1f);
    
    if( c == null )
      c = DEFAULT_COLOR;
    
    return newInstance(size, c, alpha, DEFAULT_PADDING_TOP, DEFAULT_PADDING_LEFT);
  }
  
  public static ShadowBorder newInstance(int size, Color c, float alpha,
                                         float paddingLeft, float paddingTop) {
    
    size = Math.max(size, 0);
    alpha = Math.max(alpha, 0f);
    alpha = Math.min(alpha, 1f);
    
    paddingLeft = Math.max(paddingLeft, 0f);
    paddingLeft = Math.min(paddingLeft, 1f);
    
    paddingTop = Math.max(paddingTop, 0f);
    paddingTop = Math.min(paddingTop, 1f);
    
    if( c == null )
      c = DEFAULT_COLOR;
    
    return new ShadowBorder(size, c, alpha, paddingLeft, paddingTop);
  }
  
  public static Builder newBuilder() {
    
    return new Builder();
  }
  
  public static class Builder {
    
    private int size          = DEFAULT_SIZE;
    private Color color       = DEFAULT_COLOR;
    private float alpha       = DEFAULT_ALPHA;
    
    private float paddingLeft = DEFAULT_PADDING_LEFT;
    private float paddingTop  = DEFAULT_PADDING_TOP;
    
    // use static method ShadowBorder.newBuilder().
    private Builder() {}
    
    public Builder shadowSize(int size) {
      
      if( size < 0 )
        throw new IllegalArgumentException("Invalid shadow size: " + size);
      
      this.size = size;
      return this;
    }
    
    public Builder shadowColor(Color color) {
      
      if( color == null )
        throw new NullPointerException();
      
      this.color = color;
      return this;
    }
    
    public Builder shadowAlpha(float alpha) {
      
      alpha = Math.max(alpha, 0f);
      alpha = Math.min(alpha, 1f);
      
      this.alpha = alpha;
      return this;
    }
    
    public Builder paddingLeft(float paddingLeft) {
      
      paddingLeft = Math.max(paddingLeft, 0f);
      paddingLeft = Math.min(paddingLeft, 1f);
      
      this.paddingLeft = paddingLeft;
      return this;
    }
    
    public Builder paddingTop(float paddingTop) {
      
      paddingTop = Math.max(paddingTop, 0f);
      paddingTop = Math.min(paddingTop, 1f);
      
      this.paddingTop = paddingTop;
      return this;
    }
    
    public Builder topLeft() {
      
      return paddingLeft(0f).paddingTop(0f);
    }
    
    public Builder left() {
      
      return paddingLeft(0f).paddingTop(0.5f);
    }
    
    public Builder bottomLeft() {
      
      return paddingLeft(0f).paddingTop(1f);
    }
    
    public Builder bottom() {
      
      return paddingLeft(0.5f).paddingTop(1f);
    }
    
    public Builder bottomRight() {
      
      return paddingLeft(1f).paddingTop(1f);
    }
    
    public Builder right() {
      
      return paddingLeft(1f).paddingTop(0.5f);
    }
    
    public Builder topRight() {
      
      return paddingLeft(1f).paddingTop(0f);
    }
    
    public Builder top() {
      
      return paddingLeft(0.5f).paddingTop(0f);
    }
    
    public Builder center() {
      
      return paddingLeft(0.5f).paddingTop(0.5f);
    }
    
    public ShadowBorder build() {
      
      return new ShadowBorder(size, color, alpha,
                              paddingLeft, paddingTop);
    }
  }
  
  private BufferedImage[] shadows;
  private final int size;
  private final Color color;
  private final float alpha;
  private final Insets insetsMax, insetsActual;
  private ShadowBorder(int size, Color color, float alpha, 
                       float paddingLeft, float paddingTop) {
    
    assert size > 0;
    assert color != null;
    
    assert alpha > 0f;
    assert alpha <= 1f;
    
    assert paddingLeft >= 0f;
    assert paddingLeft <= 1f;
    
    assert paddingTop <= 0f;
    assert paddingTop >= 1f;
    
    this.size = size;
    this.color = color;
    this.alpha = alpha;
    
    int dsize = size * 2;
    insetsMax = new Insets(dsize, dsize, dsize, dsize);
    insetsActual = new Insets(0, 0, 0, 0);
    insetsActual.left = Math.max(0, Math.min(dsize, Math.round(dsize * paddingLeft)));
    insetsActual.top = Math.max(0, Math.min(dsize, Math.round(dsize * paddingTop)));
    insetsActual.right = dsize - insetsActual.left;
    insetsActual.bottom = dsize - insetsActual.top;
  }
  
  @Override
  @SuppressWarnings("unchecked")
  public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
    
    Rectangle bounds = new Rectangle(x, y, width, height);
    
    @SuppressWarnings("unchecked")
    Graphics2D gg = (Graphics2D)g.create();

    Insets insets = getBorderInsets(c);
    Area shadowArea = new Area(bounds);
    shadowArea.subtract( new Area( new Rectangle(

            bounds.x + insets.left,
            bounds.y + insets.top,
            bounds.width - insets.left - insets.right,
            bounds.height - insets.top - insets.bottom
    )));

    gg.setClip(shadowArea);

    // expensive hack =)
    if( c.isOpaque() ) {

      @SuppressWarnings("unchecked")
      Graphics2D ghack = (Graphics2D) gg.create();
      hackOpaque(c, ghack, bounds);
      ghack.dispose();
    }
    
    for(Edge edge : Edge.values()) {
      
      edge.fill(this, gg, bounds);
    }
    
    gg.dispose();
  }
  
  private void hackOpaque(Component c, Graphics2D g, Rectangle bounds) {

    List<Component> chain = new ArrayList<Component>();
    chain.add(c);
    for(Component component = c.getParent(); 
        component != null;
        component = component.getParent()) {
      
      Component child = chain.get(chain.size()-1);
      g.translate(-child.getX(), -child.getY());
      chain.add(component);
      if( component.isOpaque() )
        break;
    }

    Method method = getPaintComponentMethod();
    if (method == null)
      return;

    try {

      for (int i = chain.size() - 1; i > 0; i--) {

        Component component = chain.get(i);
        method.setAccessible(true);
        method.invoke(component, g);
        g.translate(chain.get(i - 1).getX(), chain.get(i - 1).getY());
      }
    }
    catch (Exception ex) {

      // do nothing
    }
  }

  private Method getPaintComponentMethod() {

    for (Method method : JComponent.class.getDeclaredMethods()) {

      if (!method.getName().equals("paintComponent"))
        continue;

      Class<?>[] paramTypes = method.getParameterTypes();
      if (paramTypes.length == 1 && paramTypes[0].equals(Graphics.class))
        return method;
    }
    return null; // theoretically should not happen.
  }
  
  private BufferedImage[] getShadowPieces() {
    
    assert SwingUtilities.isEventDispatchThread();
    
    if( shadows == null ) {
      
      Rectangle bufferBounds = new Rectangle(0, 0, size * 10, size * 10);
      int widthFix = bufferBounds.width - (size * 2);
      int heightFix = bufferBounds.height - (size * 2);
      
      BufferedImage shadow = new BufferedImage(
              bufferBounds.width, 
              bufferBounds.height, 
              BufferedImage.TYPE_INT_ARGB);
      
      Graphics2D imgGraphics = shadow.createGraphics();
      imgGraphics.setColor(color);

      imgGraphics.fillRect(size, size, widthFix, heightFix);

      imgGraphics.setComposite(AlphaComposite.getInstance(
              AlphaComposite.SRC_IN, alpha));
      imgGraphics.fillRect(0, 0, shadow.getWidth(), shadow.getHeight());
      imgGraphics.dispose();

      GaussianFilter filter = new GaussianFilter(size * 2);
      shadow = filter.filter(shadow, null);
      
      Edge[] edges = Edge.values();
      shadows = new BufferedImage[edges.length];
      Rectangle r = new Rectangle();
      for(Edge edge : edges) {
        
        edge.locate(bufferBounds, insetsMax, r);
        shadows[edge.ordinal()] = shadow.getSubimage(r.x, r.y, r.width, r.height);
      }
    }
    return shadows;
  }

  @Override
  @SuppressWarnings("unchecked")
  public Insets getBorderInsets(Component c) {
    
    // defensive copy
    return (Insets)insetsActual.clone();
  }
  
  @Override
  public boolean isBorderOpaque() {
    
    return true;
  }
  
  private static enum Edge {
    
    TopLeft {
      
      @Override
      int x(Rectangle r, Insets i) {
        
        return r.x;
      }
      
      @Override
      int y(Rectangle r, Insets i) {
        
        return r.y;
      }
      
      @Override
      int width(Rectangle r, Insets i) {
        
        return i.left;
      }
      
      @Override
      int height(Rectangle r, Insets i) {
        
        return i.top;
      }
    },
    
    Left {
      
      @Override
      int x(Rectangle r, Insets i) {
        
        return r.x;
      }
      
      @Override
      int y(Rectangle r, Insets i) {
        
        return r.y + i.top;
      }
      
      @Override
      int width(Rectangle r, Insets i) {
        
        return i.left;
      }
      
      @Override
      int height(Rectangle r, Insets i) {
        
        return r.height - i.top - i.bottom;
      }
      
      @Override
      void fill(ShadowBorder b, Graphics2D g, Rectangle r) {
        
        BufferedImage image = b.getShadowPieces()[ordinal()];

        Rectangle clipRec = new Rectangle();
        locate(r, b.insetsMax, clipRec);

        Shape originalClip = g.getClip();
        Area clip = new Area(originalClip);
        clip.intersect(new Area(clipRec));
        g.setClip(clip);
        
        for(int x = clipRec.x, y = clipRec.y;
                y < clipRec.y + clipRec.height;
                y += image.getHeight()) {
          
          g.drawImage(image, x, y, null);
        }

        g.setClip(originalClip);
      }
    },
    
    BottomLeft {
      
      @Override
      int x(Rectangle r, Insets i) {
        
        return r.x;
      }
      
      @Override
      int y(Rectangle r, Insets i) {
        
        return r.y + r.height - i.bottom;
      }
      
      @Override
      int width(Rectangle r, Insets i) {
        
        return i.left;
      }
      
      @Override
      int height(Rectangle r, Insets i) {
        
        return i.bottom;
      }
    },
    
    Bottom {
      
      @Override
      int x(Rectangle r, Insets i) {
        
        return r.x + i.left;
      }
      
      @Override
      int y(Rectangle r, Insets i) {
        
        return r.y + r.height - i.bottom;
      }
      
      @Override
      int width(Rectangle r, Insets i) {
        
        return r.width - i.left - i.right;
      }
      
      @Override
      int height(Rectangle r, Insets i) {
        
        return i.bottom;
      }
      
      @Override
      void fill(ShadowBorder b, Graphics2D g, Rectangle r) {
        
        BufferedImage image = b.getShadowPieces()[ordinal()];

        Rectangle clipRec = new Rectangle();
        locate(r, b.insetsMax, clipRec);

        Shape originalClip = g.getClip();
        Area clip = new Area(originalClip);
        clip.intersect(new Area(clipRec));
        g.setClip(clip);
        
        for(int x = clipRec.x, y = clipRec.y;
                x < clipRec.x + clipRec.width;
                x += image.getWidth()) {
          
          g.drawImage(image, x, y, null);
        }

        g.setClip(originalClip);
      }
    },
    
    BottomRight {
      
      @Override
      int x(Rectangle r, Insets i) {
        
        return r.x + r.width - i.right;
      }
      
      @Override
      int y(Rectangle r, Insets i) {
        
        return r.y + r.height - i.bottom;
      }
      
      @Override
      int width(Rectangle r, Insets i) {
        
        return i.right;
      }
      
      @Override
      int height(Rectangle r, Insets i) {
        
        return i.bottom;
      }
    },
    
    Right {
      
      @Override
      int x(Rectangle r, Insets i) {
        
        return r.x + r.width - i.right;
      }
      
      @Override
      int y(Rectangle r, Insets i) {
        
        return r.y + i.top;
      }
      
      @Override
      int width(Rectangle r, Insets i) {
        
        return i.right;
      }
      
      @Override
      int height(Rectangle r, Insets i) {
        
        return r.height - i.top - i.bottom;
      }
      
      @Override
      void fill(ShadowBorder b, Graphics2D g, Rectangle r) {
        
        BufferedImage image = b.getShadowPieces()[ordinal()];

        Rectangle clipRec = new Rectangle();
        locate(r, b.insetsMax, clipRec);

        Shape originalClip = g.getClip();
        Area clip = new Area(originalClip);
        clip.intersect(new Area(clipRec));
        g.setClip(clip);
        
        for(int x = clipRec.x, y = clipRec.y;
                y < clipRec.y + clipRec.height;
                y += image.getHeight()) {
          
          g.drawImage(image, x, y, null);
        }

        g.setClip(originalClip);
      }
    },
    
    TopRight {
      
      @Override
      int x(Rectangle r, Insets i) {
        
        return r.x + r.width - i.right;
      }
      
      @Override
      int y(Rectangle r, Insets i) {
        
        return r.y;
      }
      
      @Override
      int width(Rectangle r, Insets i) {
        
        return i.right;
      }
      
      @Override
      int height(Rectangle r, Insets i) {
        
        return i.top;
      }
    },
    
    Top {
      
      @Override
      int x(Rectangle r, Insets i) {
        
        return r.x + i.left;
      }
      
      @Override
      int y(Rectangle r, Insets i) {
        
        return r.y;
      }
      
      @Override
      int width(Rectangle r, Insets i) {
        
        return r.width - i.left - i.right;
      }
      
      @Override
      int height(Rectangle r, Insets i) {
        
        return i.top;
      }
      
      @Override
      void fill(ShadowBorder b, Graphics2D g, Rectangle r) {
        
        BufferedImage image = b.getShadowPieces()[ordinal()];

        Rectangle clipRec = new Rectangle();
        locate(r, b.insetsMax, clipRec);

        Shape originalClip = g.getClip();
        Area clip = new Area(originalClip);
        clip.intersect(new Area(clipRec));
        g.setClip(clip);
        
        for(int x = clipRec.x, y = clipRec.y;
                x < clipRec.x + clipRec.width;
                x += image.getWidth()) {
          
          g.drawImage(image, x, y, null);
        }

        g.setClip(originalClip);
      }
    };
    
    abstract int x(Rectangle r, Insets i);
    abstract int y(Rectangle r, Insets i);
    abstract int width(Rectangle r, Insets i);
    abstract int height(Rectangle r, Insets i);
    
    void locate(Rectangle b, Insets i, Rectangle r) {
      
      r.x = x(b, i);
      r.y = y(b, i);
      r.width = width(b, i);
      r.height = height(b, i);
    }
    
    void fill(ShadowBorder b, Graphics2D g, Rectangle r) {
      
      BufferedImage image = b.getShadowPieces()[ordinal()];
      
      Rectangle clipRec = new Rectangle();
      locate(r, b.insetsMax, clipRec);
      
      Shape originalClip = g.getClip();
      Area clip = new Area(originalClip);
      clip.intersect(new Area(clipRec));
      g.setClip(clip);
      
      g.drawImage(image, clipRec.x, clipRec.y, null);
      
      g.setClip(originalClip);
    }
  }
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章