com.alee.utils.ninepatch.NinePatchIcon Maven / Gradle / Ivy
Show all versions of weblaf-ui Show documentation
/*
* This file is part of WebLookAndFeel library.
*
* WebLookAndFeel library is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* WebLookAndFeel library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WebLookAndFeel library. If not, see .
*/
package com.alee.utils.ninepatch;
import com.alee.utils.ImageUtils;
import com.alee.utils.NinePatchUtils;
import com.thoughtworks.xstream.annotations.XStreamConverter;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* This class allows you to create and use nine-patch icons within Swing applications.
*
* Basically it parses nine-patch image data (patches at the side of .9.png image) into understandable values and uses them to stretch the
* image properly when it is painted anywhere.
*
* @author Mikle Garin
* @see com.alee.painter.common.NinePatchIconPainter
* @see com.alee.painter.common.NinePatchStatePainter
*/
@XStreamConverter (NinePatchIconConverter.class)
public class NinePatchIcon implements Icon
{
/**
* Component onto which this nine-patch icon will be stretched.
*/
protected Component component;
/**
* Raw image without patches.
*/
protected BufferedImage rawImage;
/**
* Horizontal stretch intervals taken from image patches (top image patches).
* Note that pixel parts are also included here but marked with "pixel=true" boolean value.
*/
protected List horizontalStretch;
/**
* Vertical stretch intervals taken from image patches (left image patches).
* Note that pixel parts are also included here but marked with "pixel=true" boolean value.
*/
protected List verticalStretch;
/**
* Content margin taken from image patches (right and bottom patches).
* This margin is generally valuable for components which uses this icon as a background to set their style margins properly.
*/
protected Insets margin;
/**
* Cached fixed areas width of the nine-patch image with additional 1px for each stretchable area.
*/
protected Integer cachedWidth0;
/**
* Cached fixed areas width of the nine-patch image.
*/
protected Integer cachedWidth1;
/**
* Cached fixed areas height of the nine-patch image with additional 1px for each stretchable area.
*/
protected Integer cachedHeight0;
/**
* Cached fixed areas height of the nine-patch image.
*/
protected Integer cachedHeight1;
/**
* Constructs new NinePatchIcon using the nine-patch image from the specified URL.
*
* @param url nine-patch image URL
*/
public NinePatchIcon ( final URL url )
{
this ( url, null );
}
/**
* Constructs new NinePatchIcon using the nine-patch image from the specified URL.
*
* @param url nine-patch image URL
* @param component component atop of which icon will be stretched
*/
public NinePatchIcon ( final URL url, final Component component )
{
this ( ImageUtils.getBufferedImage ( url ), component );
}
/**
* Constructs new NinePatchIcon using the nine-patch image from the specified path.
*
* @param iconSrc nine-patch image path
*/
public NinePatchIcon ( final String iconSrc )
{
this ( iconSrc, null );
}
/**
* Constructs new NinePatchIcon using the nine-patch image from the specified path.
*
* @param iconSrc nine-patch image path
* @param component component atop of which icon will be stretched
*/
public NinePatchIcon ( final String iconSrc, final Component component )
{
this ( ImageUtils.getBufferedImage ( iconSrc ), component );
}
/**
* Constructs new NinePatchIcon using the specified nine-patch image.
*
* @param imageIcon nine-patch image
*/
public NinePatchIcon ( final ImageIcon imageIcon )
{
this ( imageIcon, null );
}
/**
* Constructs new NinePatchIcon using the specified nine-patch image.
*
* @param imageIcon nine-patch image
* @param component component atop of which icon will be stretched
*/
public NinePatchIcon ( final ImageIcon imageIcon, final Component component )
{
this ( ImageUtils.getBufferedImage ( imageIcon ), component );
}
/**
* Constructs new NinePatchIcon using the specified nine-patch image.
*
* @param image nine-patch image
*/
public NinePatchIcon ( final Image image )
{
this ( image, null );
}
/**
* Constructs new NinePatchIcon using the specified nine-patch image.
*
* @param image nine-patch image
* @param component component atop of which icon will be stretched
*/
public NinePatchIcon ( final Image image, final Component component )
{
this ( ImageUtils.getBufferedImage ( image ), component );
}
/**
* Constructs new NinePatchIcon using the specified nine-patch image.
*
* @param bufferedImage nine-patch image
*/
public NinePatchIcon ( final BufferedImage bufferedImage )
{
this ( bufferedImage, null );
}
/**
* Constructs new NinePatchIcon using the specified nine-patch image.
*
* @param bufferedImage nine-patch image
* @param component component atop of which icon will be stretched
*/
public NinePatchIcon ( final BufferedImage bufferedImage, final Component component )
{
this ( bufferedImage, component, true );
}
/**
* Constructs new NinePatchIcon using the specified nine-patch image.
*
* @param bufferedImage nine-patch image
* @param component component atop of which icon will be stretched
* @param parsePatches whether should parse image patches or not
*/
protected NinePatchIcon ( final BufferedImage bufferedImage, final Component component, final boolean parsePatches )
{
super ();
// Componet for which this icon will be streched
this.component = component;
// Parsing patches or creating new 9-patch icon
if ( parsePatches )
{
// Incorrect image
if ( bufferedImage.getWidth () < 3 || bufferedImage.getHeight () < 3 )
{
throw new IllegalArgumentException ( "Buffered image must be atleast 3x3 pixels size" );
}
// Creating actual image in a compatible format
final int w = bufferedImage.getWidth () - 2;
final int h = bufferedImage.getHeight () - 2;
rawImage = ImageUtils.createCompatibleImage ( bufferedImage, w, h );
final Graphics2D g2d = rawImage.createGraphics ();
g2d.drawImage ( bufferedImage, 0, 0, w, h, 1, 1, bufferedImage.getWidth () - 1, bufferedImage.getHeight () - 1, null );
g2d.dispose ();
// Parsing stretch variables
horizontalStretch = NinePatchUtils.parseIntervals ( bufferedImage, NinePatchIntervalType.horizontalStretch );
verticalStretch = NinePatchUtils.parseIntervals ( bufferedImage, NinePatchIntervalType.verticalStretch );
// Incorrect image
if ( !( ( horizontalStretch.size () > 1 || horizontalStretch.size () == 1 && !horizontalStretch.get ( 0 ).isPixel () ) &&
( verticalStretch.size () > 1 || verticalStretch.size () == 1 && !verticalStretch.get ( 0 ).isPixel () ) ) )
{
throw new IllegalArgumentException ( "There must be stretch constraints specified on image" );
}
// Parsing content margins
final List vc = NinePatchUtils.parseIntervals ( bufferedImage, NinePatchIntervalType.verticalContent );
final List hc = NinePatchUtils.parseIntervals ( bufferedImage, NinePatchIntervalType.horizontalContent );
final int top = vc.size () == 0 ? 0 : vc.get ( 0 ).getStart ();
final int bottom = vc.size () == 0 ? 0 : rawImage.getHeight () - vc.get ( 0 ).getEnd () - 1;
final int left = hc.size () == 0 ? 0 : hc.get ( 0 ).getStart ();
final int right = hc.size () == 0 ? 0 : rawImage.getWidth () - hc.get ( 0 ).getEnd () - 1;
margin = new Insets ( top, left, bottom, right );
// Forcing cached data calculation on initialization
getFixedPixelsWidth ( true );
getFixedPixelsWidth ( false );
getFixedPixelsHeight ( true );
getFixedPixelsHeight ( false );
}
else
{
// Actual image
this.rawImage = bufferedImage;
// Stretch variables
horizontalStretch = new ArrayList ();
verticalStretch = new ArrayList ();
}
}
/**
* Returns newly created NinePatchIcon with empty patches.
*
* @param rawImage raw image without patches
* @return newly created NinePatchIcon with empty patches
*/
public static NinePatchIcon create ( final BufferedImage rawImage )
{
return new NinePatchIcon ( rawImage, null, false );
}
/**
* Returns raw image without patches.
*
* @return raw image without patches
*/
public BufferedImage getRawImage ()
{
return rawImage;
}
/**
* Returns component atop of which icon will be stretched.
*
* @return component atop of which icon will be stretched
*/
public Component getComponent ()
{
return component;
}
/**
* Sets component atop of which icon will be stretched.
*
* @param component component atop of which icon will be stretched
*/
public void setComponent ( final Component component )
{
this.component = component;
}
/**
* Horizontal image patches data
*/
/**
* Returns list of horizontal stretch intervals taken from image patches.
*
* @return list of horizontal stretch intervals taken from image patches
*/
public List getHorizontalStretch ()
{
return horizontalStretch;
}
/**
* Sets list of horizontal stretch intervals.
*
* @param horizontalStretch list of horizontal stretch intervals
*/
public void setHorizontalStretch ( final List horizontalStretch )
{
this.horizontalStretch = horizontalStretch;
updateCachedWidthData ();
}
/**
* Adds horizontal stretch interval.
*
* @param interval horizontal stretch interval to add
*/
public void addHorizontalStretch ( final NinePatchInterval interval )
{
this.horizontalStretch.add ( interval );
updateCachedWidthData ();
}
/**
* Adds horizontal stretch interval.
*
* @param start interval start
* @param end interval end
* @param pixel whether fixed interval or not
*/
public void addHorizontalStretch ( final int start, final int end, final boolean pixel )
{
addHorizontalStretch ( new NinePatchInterval ( start, end, pixel ) );
}
/**
* Returns list of vertical stretch intervals taken from image patches.
*
* @return list of vertical stretch intervals taken from image patches
*/
public List getVerticalStretch ()
{
return verticalStretch;
}
/**
* Sets list of vertical stretch intervals.
*
* @param verticalStretch list of vertical stretch intervals
*/
public void setVerticalStretch ( final List verticalStretch )
{
this.verticalStretch = verticalStretch;
updateCachedHeightData ();
}
/**
* Adds vertical stretch interval.
*
* @param interval vertical stretch interval to add
*/
public void addVerticalStretch ( final NinePatchInterval interval )
{
this.verticalStretch.add ( interval );
updateCachedHeightData ();
}
/**
* Adds vertical stretch interval.
*
* @param start interval start
* @param end interval end
* @param pixel whether fixed interval or not
*/
public void addVerticalStretch ( final int start, final int end, final boolean pixel )
{
addVerticalStretch ( new NinePatchInterval ( start, end, pixel ) );
}
/**
* Returns margin taken from image content patches.
*
* @return margin taken from image content patches
*/
public Insets getMargin ()
{
return ( Insets ) margin.clone ();
}
/**
* Returns margin taken from image stretch patches.
*
* @return margin taken from image stretch patches
*/
public Insets getStretchMargin ()
{
final NinePatchInterval top = verticalStretch.get ( 0 );
final NinePatchInterval left = horizontalStretch.get ( 0 );
final NinePatchInterval bottom = verticalStretch.get ( verticalStretch.size () - 1 );
final NinePatchInterval right = horizontalStretch.get ( horizontalStretch.size () - 1 );
return new Insets ( top.getLength (), left.getLength (), bottom.getLength (), right.getLength () );
}
/**
* Sets content margin.
*
* @param margin content margin
*/
public void setMargin ( final Insets margin )
{
this.margin = margin;
}
/**
* Sets content margin.
*
* @param top top margin
* @param left left margin
* @param bottom bottom margin
* @param right right margin
*/
public void setMargin ( final int top, final int left, final int bottom, final int right )
{
setMargin ( new Insets ( top, left, bottom, right ) );
}
/**
* Sets content margin.
*
* @param spacing sides margin
*/
public void setMargin ( final int spacing )
{
setMargin ( spacing, spacing, spacing, spacing );
}
/**
* Paints icon for the specified component.
*
* @param c component to process
* @param g graphics context
*/
public void paintIcon ( final Component c, final Graphics g )
{
paintIcon ( ( Graphics2D ) g, 0, 0, getIconWidth ( c ), getIconHeight ( c ) );
}
/**
* Paints icon for the specified component at the specified location.
*
* @param c component to process
* @param g graphics context
* @param x location X coordinate
* @param y location Y coordinate
*/
@Override
public void paintIcon ( final Component c, final Graphics g, final int x, final int y )
{
paintIcon ( ( Graphics2D ) g, x, y, getFixedPixelsWidth ( true ), getFixedPixelsHeight ( true ) );
}
/**
* Paints icon at the specified bounds.
*
* @param g2d graphics context
* @param bounds icon bounds
*/
public void paintIcon ( final Graphics2D g2d, final Rectangle bounds )
{
paintIcon ( g2d, bounds.x, bounds.y, bounds.width, bounds.height );
}
/**
* Paints icon at the specified bounds.
*
* @param g2d graphics context
* @param x location X coordinate
* @param y location Y coordinate
* @param width icon width
* @param height icon height
*/
public void paintIcon ( final Graphics2D g2d, final int x, final int y, final int width, final int height )
{
final int availableWidth = Math.max ( width, getFixedPixelsWidth ( true ) );
final int availableHeight = Math.max ( height, getFixedPixelsHeight ( true ) );
final int fixedPixelsX = getFixedPixelsWidth ( false );
final int unfixedX = availableWidth - fixedPixelsX;
final int fixedPixelsY = getFixedPixelsHeight ( false );
final int unfixedY = availableHeight - fixedPixelsY;
int currentY = y;
for ( final NinePatchInterval intervalY : verticalStretch )
{
// Percent part height
final int intervalHeight = intervalY.getEnd () - intervalY.getStart () + 1;
final int finalHeight;
if ( intervalY.isPixel () )
{
finalHeight = intervalHeight;
}
else
{
final float percents = ( float ) intervalHeight / ( rawImage.getHeight () - fixedPixelsY );
finalHeight = Math.round ( percents * unfixedY );
}
int currentX = x;
for ( final NinePatchInterval intervalX : horizontalStretch )
{
// Percent part width
final int intervalWidth = intervalX.getEnd () - intervalX.getStart () + 1;
final int finalWidth;
if ( intervalX.isPixel () )
{
finalWidth = intervalWidth;
}
else
{
final float percents = ( float ) intervalWidth / ( rawImage.getWidth () - fixedPixelsX );
finalWidth = Math.round ( percents * unfixedX );
}
// Drawing image part
g2d.drawImage ( rawImage, currentX, currentY, currentX + finalWidth, currentY + finalHeight, intervalX.getStart (),
intervalY.getStart (), intervalX.getStart () + intervalWidth, intervalY.getStart () + intervalHeight, null );
// Icrementing current X
currentX += finalWidth;
}
// Icrementing current Y
currentY += finalHeight;
}
}
/**
* Returns cached fixed minimum width for this icon.
*
* @param addUnfixedSpaces whether to add 1px for each stretchable area or not
* @return cached fixed minimum width for this icon
*/
public int getFixedPixelsWidth ( final boolean addUnfixedSpaces )
{
if ( addUnfixedSpaces )
{
if ( cachedWidth0 == null )
{
cachedWidth0 = calculateFixedPixelsWidth ( addUnfixedSpaces );
}
return cachedWidth0;
}
else
{
if ( cachedWidth1 == null )
{
cachedWidth1 = calculateFixedPixelsWidth ( addUnfixedSpaces );
}
return cachedWidth1;
}
}
/**
* Returns fixed minimum width for this icon.
*
* @param addUnfixedSpaces whether to add 1px for each stretchable area or not
* @return fixed minimum width for this icon
*/
protected int calculateFixedPixelsWidth ( final boolean addUnfixedSpaces )
{
int fixedPixelsX = rawImage.getWidth ();
for ( final NinePatchInterval interval : horizontalStretch )
{
if ( !interval.isPixel () )
{
fixedPixelsX -= interval.getEnd () - interval.getStart () + 1;
if ( addUnfixedSpaces )
{
fixedPixelsX += 1;
}
}
}
return fixedPixelsX;
}
/**
* Clears fixed pixels width caches.
*/
protected void updateCachedWidthData ()
{
cachedWidth0 = null;
cachedWidth1 = null;
getFixedPixelsWidth ( true );
getFixedPixelsWidth ( false );
}
/**
* Returns cached fixed minimum height for this icon.
*
* @param addUnfixedSpaces whether to add 1px for each stretchable area or not
* @return cached fixed minimum height for this icon
*/
public int getFixedPixelsHeight ( final boolean addUnfixedSpaces )
{
if ( addUnfixedSpaces )
{
if ( cachedHeight0 == null )
{
cachedHeight0 = calculateFixedPixelsHeight ( addUnfixedSpaces );
}
return cachedHeight0;
}
else
{
if ( cachedHeight1 == null )
{
cachedHeight1 = calculateFixedPixelsHeight ( addUnfixedSpaces );
}
return cachedHeight1;
}
}
/**
* Returns fixed minimum height for this icon.
*
* @param addUnfixedSpaces swhether to add 1px for each stretchable area or not
* @return fixed minimum height for this icon
*/
protected int calculateFixedPixelsHeight ( final boolean addUnfixedSpaces )
{
int fixedPixelsY = rawImage.getHeight ();
for ( final NinePatchInterval interval : verticalStretch )
{
if ( !interval.isPixel () )
{
fixedPixelsY -= interval.getEnd () - interval.getStart () + 1;
if ( addUnfixedSpaces )
{
fixedPixelsY += 1;
}
}
}
return fixedPixelsY;
}
/**
* Clears fixed pixels height caches.
*/
protected void updateCachedHeightData ()
{
cachedHeight0 = null;
cachedHeight1 = null;
getFixedPixelsHeight ( true );
getFixedPixelsHeight ( false );
}
@Override
public int getIconWidth ()
{
return getIconWidth ( component );
}
/**
* Returns icon width for the specified component.
*
* @param component component atop of which icon will be stretched
* @return icon width for the specified component
*/
public int getIconWidth ( final Component component )
{
return Math.max ( component != null ? component.getWidth () : 0, getFixedPixelsWidth ( true ) );
}
@Override
public int getIconHeight ()
{
return getIconHeight ( component );
}
/**
* Returns icon height for the specified component.
*
* @param component component atop of which icon will be stretched
* @return icon height for the specified component
*/
public int getIconHeight ( final Component component )
{
return Math.max ( component != null ? component.getHeight () : 0, getFixedPixelsHeight ( true ) );
}
/**
* Returns preferred icon size.
*
* @return preferred icon size
*/
public Dimension getPreferredSize ()
{
return new Dimension ( getFixedPixelsWidth ( true ), getFixedPixelsHeight ( true ) );
}
/**
* Returns raw image size.
*
* @return raw image size
*/
public Dimension getRealImageSize ()
{
return new Dimension ( getRawImage ().getWidth (), getRawImage ().getHeight () );
}
}