GP-2389 ImageIconWrapper refactor

This commit is contained in:
ghidragon 2022-08-03 16:40:33 -04:00
parent 781c765e60
commit 30b14e3baf
15 changed files with 539 additions and 42 deletions

View File

@ -29,7 +29,7 @@ import ghidra.program.model.listing.Function;
import ghidra.util.HelpLocation;
import resources.MultiIcon;
import resources.ResourceManager;
import resources.icons.ScaledImageIconWrapper;
import resources.icons.ScaledImageIcon;
import resources.icons.TranslateIcon;
/**
@ -48,7 +48,7 @@ public abstract class CompareFunctionsAction extends DockingAction {
private static final ImageIcon COMPARISON_ICON =
ResourceManager.loadImage("images/page_white_c.png");
private static final Icon NEW_ICON = ResourceManager.loadImage("images/bullet_star.png");
private static final Icon SCALED_NEW_ICON = new ScaledImageIconWrapper(NEW_ICON, 16, 16);
private static final Icon SCALED_NEW_ICON = new ScaledImageIcon(NEW_ICON, 16, 16);
private static final Icon TRANSLATED_NEW_ICON = new TranslateIcon(SCALED_NEW_ICON, 4, -4);
private static final Icon CREATE_NEW_COMPARISON_ICON =
new MultiIcon(COMPARISON_ICON, TRANSLATED_NEW_ICON);
@ -101,8 +101,7 @@ public abstract class CompareFunctionsAction extends DockingAction {
setPopupMenuData(new MenuData(new String[] { "Compare Selected Functions" },
getToolBarIcon(), CREATE_COMPARISON_GROUP));
ToolBarData newToolBarData =
new ToolBarData(getToolBarIcon(), CREATE_COMPARISON_GROUP);
ToolBarData newToolBarData = new ToolBarData(getToolBarIcon(), CREATE_COMPARISON_GROUP);
setToolBarData(newToolBarData);
setHelpLocation(new HelpLocation("FunctionComparison", "Function_Comparison"));

View File

@ -37,7 +37,7 @@ import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import resources.MultiIcon;
import resources.ResourceManager;
import resources.icons.ScaledImageIconWrapper;
import resources.icons.ScaledImageIcon;
import resources.icons.TranslateIcon;
import util.CollectionUtils;
@ -50,7 +50,7 @@ import util.CollectionUtils;
public class OpenFunctionTableAction extends DockingAction {
private static final Icon ADD_ICON = ResourceManager.loadImage("images/Plus.png");
private static final Icon SCALED_ADD_ICON = new ScaledImageIconWrapper(ADD_ICON, 10, 10);
private static final Icon SCALED_ADD_ICON = new ScaledImageIcon(ADD_ICON, 10, 10);
private static final ImageIcon COMPARISON_ICON =
ResourceManager.loadImage("images/page_white_c.png");
private static final Icon TRANSLATED_ADD_ICON = new TranslateIcon(SCALED_ADD_ICON, 8, 1);

View File

@ -89,8 +89,8 @@ public class VTPlugin extends Plugin {
protected Icon createIcon() {
MultiIcon icon = new MultiIcon(new EmptyIcon(16, 16));
ImageIcon cancelIcon = ResourceManager.loadImage("images/dialog-cancel.png");
ScaledImageIconWrapper scaledCancelIcon =
new ScaledImageIconWrapper(cancelIcon, 13, 13);
ScaledImageIcon scaledCancelIcon =
new ScaledImageIcon(cancelIcon, 13, 13);
TranslateIcon translatedCancelIcon = new TranslateIcon(scaledCancelIcon, 3, 4);
ImageIcon undoIcon = ResourceManager.loadImage("images/undo.png");
TranslateIcon translatedUndoIcon = new TranslateIcon(undoIcon, 0, -4);

View File

@ -31,7 +31,6 @@ import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.AssertException;
import resources.ResourceManager;
import resources.icons.FileBasedIcon;
import resources.icons.ImageIconWrapper;
import utilities.util.reflection.ReflectionUtilities;
/**
@ -493,9 +492,8 @@ public abstract class DockingAction implements DockingActionIf {
}
Icon icon = menuBarData.getMenuIcon();
if (icon != null && icon instanceof ImageIconWrapper) {
ImageIconWrapper wrapper = (ImageIconWrapper) icon;
String filename = wrapper.getFilename();
if (icon instanceof FileBasedIcon filebasedIcon) {
String filename = filebasedIcon.getFilename();
buffer.append(" MENU ICON: ").append(filename);
buffer.append('\n');
}
@ -522,9 +520,8 @@ public abstract class DockingAction implements DockingActionIf {
}
Icon icon = popupMenuData.getMenuIcon();
if (icon != null && icon instanceof ImageIconWrapper) {
ImageIconWrapper wrapper = (ImageIconWrapper) icon;
String filename = wrapper.getFilename();
if (icon instanceof FileBasedIcon fileBasedIcon) {
String filename = fileBasedIcon.getFilename();
buffer.append(" POPUP ICON: ").append(filename);
buffer.append('\n');
}

View File

@ -25,7 +25,7 @@ import javax.swing.ImageIcon;
import resources.MultiIcon;
import resources.ResourceManager;
import resources.icons.EmptyIcon;
import resources.icons.ScaledImageIconWrapper;
import resources.icons.ScaledImageIcon;
/**
* An icon that allows sub-icons to be added at key perimeter locations. Each position can
@ -317,7 +317,7 @@ public class BadgedIcon implements Icon {
MultiIcon icon = badgeMap.get(pos);
Icon scaled = new ScaledImageIconWrapper(icon, badgeSize.width, badgeSize.height);
Icon scaled = new ScaledImageIcon(icon, badgeSize.width, badgeSize.height);
Point badgePaintLoc = getBadgePaintLocation(pos, badgeSize);

View File

@ -324,7 +324,7 @@ public class ResourceManager {
* @return A new, scaled ImageIcon
*/
public static ImageIcon getScaledIcon(Icon icon, int width, int height, int hints) {
return new ScaledImageIconWrapper(icon, width, height, hints);
return new ScaledImageIcon(icon, width, height, hints);
}
/**
@ -337,7 +337,7 @@ public class ResourceManager {
* @return A new, scaled ImageIcon
*/
public static ImageIcon getScaledIcon(Icon icon, int width, int height) {
return new ScaledImageIconWrapper(icon, width, height);
return new ScaledImageIcon(icon, width, height);
}
/**
@ -346,7 +346,7 @@ public class ResourceManager {
* @return disabled icon
*/
public static ImageIcon getDisabledIcon(Icon icon) {
return new DisabledImageIconWrapper(getImageIcon(icon));
return new DisabledImageIcon(icon);
}
/**
@ -355,7 +355,7 @@ public class ResourceManager {
* @return disabled icon
*/
public static ImageIcon getDisabledIcon(ImageIcon icon) {
return new DisabledImageIconWrapper(icon);
return new DisabledImageIcon(icon);
}
/**
@ -367,7 +367,7 @@ public class ResourceManager {
* @return a disabled version of the original icon.
*/
public static ImageIcon getDisabledIcon(Icon icon, int brightnessPercent) {
return new DisabledImageIconWrapper(icon, brightnessPercent);
return new DisabledImageIcon(icon, brightnessPercent);
}
/**
@ -381,7 +381,7 @@ public class ResourceManager {
* @return the new icon
*/
public static ImageIcon getImageIconFromImage(String imageName, Image image) {
return new ImageIconWrapper(image, imageName);
return new DerivedImageIcon(imageName, image);
}
/**
@ -396,7 +396,7 @@ public class ResourceManager {
if (icon instanceof ImageIcon) {
return (ImageIcon) icon;
}
return new ImageIconWrapper(icon);
return new DerivedImageIcon(icon);
}
/**
@ -452,7 +452,7 @@ public class ResourceManager {
if (icon != null) {
return icon;
}
icon = new ImageIconWrapper(imageBytes, imageName);
icon = new BytesImageIcon(imageName, imageBytes);
iconMap.put(imageName, icon);
return icon;
}
@ -476,39 +476,47 @@ public class ResourceManager {
/**
* Load the image specified by filename; returns the default bomb icon
* if problems occur trying to load the file.
* <p>
*
* @param filename name of file to load, e.g., "images/home.gif"
* @return the image icon stored in the bytes
*/
public static ImageIcon loadImage(String filename) {
// use the wrapper so that images are not loaded until they are needed
ImageIcon icon = iconMap.get(filename);
if (icon != null) {
return icon;
if (icon == null) {
icon = doLoadIcon(filename, ResourceManager.getDefaultIcon());
iconMap.put(filename, icon);
}
return icon;
}
private static ImageIcon doLoadIcon(String filename, ImageIcon defaultIcon) {
// if only the name of an icon is given, but not a path, check to see if it is
// a resource that lives under our "images/" folder
if (!filename.contains("/")) {
URL url = getResource("images/" + filename);
if (url != null) {
return new UrlImageIcon(filename, url);
}
}
// look for it directly with the given path
URL url = getResource(filename);
if (url != null) {
return new UrlImageIcon(filename, url);
}
// try using the filename as a file path
File imageFile = new File(filename);
if (imageFile.exists()) {
try {
icon = new ImageIconWrapper(imageFile.toURI().toURL());
iconMap.put(filename, icon);
return icon;
return new UrlImageIcon(filename, imageFile.toURI().toURL());
}
catch (MalformedURLException e) {
// handled below
}
}
URL url = getResource(filename);
if (url != null) {
icon = new ImageIconWrapper(url);
iconMap.put(filename, icon);
return icon;
}
return getDefaultIcon();
return defaultIcon;
}
/**
@ -547,7 +555,7 @@ public class ResourceManager {
Msg.error(ResourceManager.class,
"Could not find default icon: " + DEFAULT_ICON_FILENAME);
}
DEFAULT_ICON = new ImageIconWrapper(url);
DEFAULT_ICON = new UrlImageIcon(DEFAULT_ICON_FILENAME, url);
}
return DEFAULT_ICON;
}

View File

@ -0,0 +1,49 @@
/* ###
* IP: GHIDRA
*
* 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.
*/
package resources.icons;
import java.awt.Image;
import java.awt.Toolkit;
import java.util.Objects;
import javax.swing.ImageIcon;
import generic.util.image.ImageUtils;
/**
* {@link LazyImageIcon} that is created from a byte array
*/
public class BytesImageIcon extends LazyImageIcon {
private byte[] bytes;
public BytesImageIcon(String name, byte[] imageBytes) {
super(name);
this.bytes = Objects.requireNonNull(imageBytes);
}
protected ImageIcon createImageIcon() {
String name = getFilename();
Image image = createImage();
if (!ImageUtils.waitForImage(name, image)) {
return null;
}
return new ImageIcon(image, name);
}
protected Image createImage() {
return Toolkit.getDefaultToolkit().createImage(bytes);
}
}

View File

@ -0,0 +1,80 @@
/* ###
* IP: GHIDRA
*
* 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.
*/
package resources.icons;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.util.Objects;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import generic.util.image.ImageUtils;
import resources.ResourceManager;
/**
* {@link LazyImageIcon} that is created from an {@link Icon} or an {@link Image}
*/
public class DerivedImageIcon extends LazyImageIcon {
private Icon sourceIcon;
private Image sourceImage;
/**
* Constructor for deriving from an icon
* @param icon the source icon
*/
public DerivedImageIcon(Icon icon) {
super(ResourceManager.getIconName(icon));
this.sourceIcon = Objects.requireNonNull(icon);
}
/**
* Constructor for deriving from an image
* @param name the name of the image
* @param image the source image
*/
public DerivedImageIcon(String name, Image image) {
super(name);
this.sourceImage = Objects.requireNonNull(image);
}
protected ImageIcon createImageIcon() {
Image image = createImage();
String imageName = getFilename();
if (!ImageUtils.waitForImage(imageName, image)) {
return null;
}
return new ImageIcon(image, imageName);
}
protected Image createImage() {
if (sourceImage != null) {
return sourceImage;
}
// if sourceImage is null, then sourceIcon can't be null
if (sourceIcon instanceof ImageIcon) {
return ((ImageIcon) sourceIcon).getImage();
}
BufferedImage bufferedImage = new BufferedImage(sourceIcon.getIconWidth(),
sourceIcon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics graphics = bufferedImage.getGraphics();
sourceIcon.paintIcon(null, graphics, 0, 0);
graphics.dispose();
return bufferedImage;
}
}

View File

@ -0,0 +1,64 @@
/* ###
* IP: GHIDRA
*
* 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.
*/
package resources.icons;
import java.awt.Image;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import generic.util.image.ImageUtils;
/**
* {@link LazyImageIcon} that creates a disabled version of an icon
*/
public class DisabledImageIcon extends DerivedImageIcon {
/**
* The inverse percentage of gray (higher percentage equals less gray) to apply to
* the disabled image; higher is brighter
*/
private int brightnessPercent;
/**
* Construct wrapped disabled ImageIcon based upon specified baseIcon.
* A 50% brightness will be applied.
* @param baseIcon enabled icon to be rendered as disabled
*/
public DisabledImageIcon(Icon baseIcon) {
this(baseIcon, 50); // default to half gray
}
/**
* Construct wrapped disabled ImageIcon based upon specified baseIcon
* using the specified brightness level
* @param baseIcon the icon to create a disabled version of
* @param brightnessPercent a brightness level specified using a
* value in the range of 0 thru 100.
*/
public DisabledImageIcon(Icon baseIcon, int brightnessPercent) {
super(baseIcon);
this.brightnessPercent = brightnessPercent;
}
@Override
protected ImageIcon createImageIcon() {
Image image = createImage();
Image disabledImage = ImageUtils.createDisabledImage(image, brightnessPercent);
return new ImageIcon(disabledImage, getFilename());
}
}

View File

@ -22,6 +22,12 @@ import javax.swing.ImageIcon;
import generic.util.image.ImageUtils;
/**
* Creates a disabled version of an icon
* @deprecated This class has been replaced by {@link DisabledImageIcon} since it
* extends {@link ImageIconWrapper} which has also been deprecated.
*/
@Deprecated(forRemoval = true, since = "11")
public class DisabledImageIconWrapper extends ImageIconWrapper {
/**

View File

@ -36,7 +36,13 @@ import resources.ResourceManager;
* it has the added benefit of allowing the use of static initialization
* of ImageIcons without starting the Swing thread which can cause
* problems when running headless.
*
* @deprecated This class has been replaced by a series of classes that extend
* {@link LazyImageIcon}: {@link UrlImageIcon}, {@link DerivedImageIcon}, {@link BytesImageIcon},
* {@link DisabledImageIcon}, and {@link ScaledImageIcon}. Pick the one that matches
* the constructor that was being used to create an ImageIconWrapper
*/
@Deprecated(forRemoval = true, since = "11")
public class ImageIconWrapper extends ImageIcon implements FileBasedIcon {
private boolean loaded;

View File

@ -0,0 +1,134 @@
/* ###
* IP: GHIDRA
*
* 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.
*/
package resources.icons;
import java.awt.*;
import java.awt.image.ImageObserver;
import javax.accessibility.AccessibleContext;
import javax.swing.ImageIcon;
import resources.ResourceManager;
/**
* <code>LazyImageIcon</code> provides the ability to instantiate
* an ImageIcon with delayed loading. In addition to delayed loading
* it has the added benefit of allowing the use of static initialization
* of ImageIcons without starting the Swing thread which can cause
* problems when running headless.
*/
public abstract class LazyImageIcon extends ImageIcon implements FileBasedIcon {
private boolean loaded;
protected LazyImageIcon(String name) {
setDescription(name);
}
private synchronized void init() {
if (!loaded) {
loaded = true;
ImageIcon imageIcon = createImageIcon();
if (imageIcon == null) {
imageIcon = getDefaultIcon();
}
super.setImage(imageIcon.getImage());
super.setDescription(getDescription());
}
}
protected abstract ImageIcon createImageIcon();
@Override
public String getFilename() {
return getDescription();
}
@Override
public Image getImage() {
init();
return super.getImage();
}
@Override
public AccessibleContext getAccessibleContext() {
init();
return super.getAccessibleContext();
}
@Override
public String getDescription() {
init();
return super.getDescription();
}
@Override
public int getIconHeight() {
init();
return super.getIconHeight();
}
@Override
public int getIconWidth() {
init();
return super.getIconWidth();
}
@Override
public int getImageLoadStatus() {
init();
return super.getImageLoadStatus();
}
@Override
public ImageObserver getImageObserver() {
init();
return super.getImageObserver();
}
@Override
public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
init();
super.paintIcon(c, g, x, y);
}
@Override
public void setDescription(String description) {
super.setDescription(description);
}
@Override
public void setImage(Image image) {
init();
super.setImage(image);
}
@Override
public String toString() {
init();
return super.toString();
}
private ImageIcon getDefaultIcon() {
ImageIcon defaultIcon = ResourceManager.getDefaultIcon();
if (this == defaultIcon) {
// this can happen under just the right conditions when loading the default
// icon's bytes fails (probably due to disk or network issues)
throw new IllegalStateException("Unexpected failure loading the default icon!");
}
return defaultIcon; // some sort of initialization has failed
}
}

View File

@ -0,0 +1,68 @@
/* ###
* IP: GHIDRA
*
* 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.
*/
package resources.icons;
import java.awt.*;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import generic.util.image.ImageUtils;
/**
* {@link LazyImageIcon} that creates a scaled version of an icon
*/
public class ScaledImageIcon extends DerivedImageIcon {
private int width;
private int height;
private int hints;
/**
* Construct wrapped scaled ImageIcon based upon specified
* baseIcon and desired size. The rendering hints of
* {@link Image#SCALE_AREA_AVERAGING} will be applied.
* @param baseIcon base icon
* @param width new icon width
* @param height new icon height
*/
public ScaledImageIcon(Icon baseIcon, int width, int height) {
this(baseIcon, width, height, Image.SCALE_AREA_AVERAGING);
}
/**
* Construct wrapped scaled ImageIcon based upon specified
* baseIcon and desired size
* @param baseIcon base icon
* @param width new icon width
* @param height new icon height
* @param hints {@link RenderingHints} used by {@link Graphics2D}
*/
public ScaledImageIcon(Icon baseIcon, int width, int height, int hints) {
super(baseIcon);
this.width = width;
this.height = height;
this.hints = hints;
}
@Override
protected ImageIcon createImageIcon() {
Image image = createImage();
Image scaledImage = ImageUtils.createScaledImage(image, width, height, hints);
return new ImageIcon(scaledImage, getFilename());
}
}

View File

@ -22,6 +22,12 @@ import javax.swing.ImageIcon;
import generic.util.image.ImageUtils;
/**
* Creates a scaled version of an icon
* @deprecated This class has been replaced by {@link ScaledImageIcon} since it
* extends {@link ImageIconWrapper} which has also been deprecated.
*/
@Deprecated(forRemoval = true, since = "11")
public class ScaledImageIconWrapper extends ImageIconWrapper {
private int width;

View File

@ -0,0 +1,80 @@
/* ###
* IP: GHIDRA
*
* 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.
*/
package resources.icons;
import java.awt.Image;
import java.awt.Toolkit;
import java.io.*;
import java.net.URL;
import java.util.Objects;
import javax.swing.ImageIcon;
import generic.util.image.ImageUtils;
import ghidra.util.Msg;
/**
* {@link LazyImageIcon} that is created from a URL to an icon file.
*/
public class UrlImageIcon extends LazyImageIcon {
private URL imageURL;
/**
* Constructor
* @param path the path String used to create the URL
* @param url the {@link URL} to an icon resource file
*/
public UrlImageIcon(String path, URL url) {
super(path);
this.imageURL = Objects.requireNonNull(url);
}
protected ImageIcon createImageIcon() {
String name = getFilename();
Image image = createImage();
if (image == null) {
return null;
}
if (!ImageUtils.waitForImage(name, image)) {
return null;
}
return new ImageIcon(image, name);
}
protected Image createImage() {
byte[] imageBytes = loadBytesFromURL(imageURL);
if (imageBytes == null) {
return null;
}
return Toolkit.getDefaultToolkit().createImage(imageBytes);
}
private byte[] loadBytesFromURL(URL url) {
try (InputStream is = url.openStream()) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
int length = 0;
byte[] buf = new byte[1024];
while ((length = is.read(buf)) > 0) {
os.write(buf, 0, length);
}
return os.toByteArray();
}
catch (IOException e) {
Msg.error(this, "Exception loading image bytes: " + url.toExternalForm(), e);
}
return null;
}
}