package edu.princeton.toy; import java.awt.*; import java.awt.image.*; import javax.swing.*; /** * TImageManager is a class that manages the jpeg and gif files which Visual X-TOY uses. * It maintains a group of unscaled images and a group of scaled images which are loaded an * rescaled upon request only. * * @author Brian Tsang * @version 7.1 */ public class TImageManager { /** * The paths to all the unscaled images, relative to the root of the jar file. */ public static final String UNSCALED_IMAGE_PATHS[][] = { { "images/splashBackground.jpg" }, { "images/frameIconWindows.gif", "images/frameIconOtherOs.gif", "images/iconNew.gif", "images/iconOpen.gif", "images/iconOpenExample.gif", "images/iconSave.gif", "images/iconSaveAs.gif", "images/iconSaveAll.gif", "images/iconEditModeOff.gif", "images/iconEditModeOn.gif", "images/iconDebugModeOff.gif", "images/iconDebugModeOn.gif", "images/iconSimModeOff.gif", "images/iconSimModeOn.gif", "images/iconReset.gif", "images/iconStep.gif", "images/iconRun.gif", "images/iconInterrupt.gif", "images/iconCheckSyntax.gif" } }; /** * The index of the startup unscaled image group. */ public static final byte STARTUP_UNSCALED_IMAGE_GROUP = (byte)0; /** * The index of the splash window's background */ public static final short STARTUP_UNSCALED_SPLASH_BACKGROUND = (short)0; /** * The index of the regular unscaled image group. */ public static final byte REGULAR_UNSCALED_IMAGE_GROUP = (byte)1; /** * The index of the icon for the frame. * * @see #getImage(byte, short) */ public static final short REGULAR_UNSCALED_FRAME_ICON_WINDOWS = (short)0; /** * The index of the icon for the frame. * * @see #getImage(byte, short) */ public static final short REGULAR_UNSCALED_FRAME_ICON_OTHER_OS = (short)1; /** * The index of the new icon for the toolbar. * * @see #getImage(byte, short) */ public static final short REGULAR_UNSCALED_NEW_ICON = (short)2; /** * The index of the open icon for the toolbar. * * @see #getImage(byte, short) */ public static final short REGULAR_UNSCALED_OPEN_ICON = (short)3; /** * The index of the open example icon for the toolbar. * * @see #getImage(byte, short) */ public static final short REGULAR_UNSCALED_OPEN_EXAMPLE_ICON = (short)4; /** * The index of the save icon for the toolbar. * * @see #getImage(byte, short) */ public static final short REGULAR_UNSCALED_SAVE_ICON = (short)5; /** * The index of the save as icon for the toolbar. * * @see #getImage(byte, short) */ public static final short REGULAR_UNSCALED_SAVE_AS_ICON = (short)6; /** * The index of the save all icon for the toolbar. * * @see #getImage(byte, short) */ public static final short REGULAR_UNSCALED_SAVE_ALL_ICON = (short)7; /** * The index of the edit mode off icon for the toolbar. * * @see #getImage(byte, short) */ public static final short REGULAR_UNSCALED_EDIT_MODE_ICON_OFF = (short)8; /** * The index of the edit mode on icon for the toolbar. * * @see #getImage(byte, short) */ public static final short REGULAR_UNSCALED_EDIT_MODE_ICON_ON = (short)9; /** * The index of the debug mode off icon for the toolbar. * * @see #getImage(byte, short) */ public static final short REGULAR_UNSCALED_DEBUG_MODE_ICON_OFF = (short)10; /** * The index of the debug mode on icon for the toolbar. * * @see #getImage(byte, short) */ public static final short REGULAR_UNSCALED_DEBUG_MODE_ICON_ON = (short)11; /** * The index of the sim mode off icon for the toolbar. * * @see #getImage(byte, short) */ public static final short REGULAR_UNSCALED_SIM_MODE_ICON_OFF = (short)12; /** * The index of the sim mode on icon for the toolbar. * * @see #getImage(byte, short) */ public static final short REGULAR_UNSCALED_SIM_MODE_ICON_ON = (short)13; /** * The index of the reset icon for the toolbar. * * @see #getImage(byte, short) */ public static final short REGULAR_UNSCALED_RESET_ICON = (short)14; /** * The index of the step icon for the toolbar. * * @see #getImage(byte, short) */ public static final short REGULAR_UNSCALED_STEP_ICON = (short)15; /** * The index of the run icon for the toolbar. * * @see #getImage(byte, short) */ public static final short REGULAR_UNSCALED_RUN_ICON = (short)16; /** * The index of the interrupt icon for the toolbar. * * @see #getImage(byte, short) */ public static final short REGULAR_UNSCALED_INTERRUPT_ICON = (short)17; /** * The index of the check syntax icon for the toolbar. * * @see #getImage(byte, short) */ public static final short REGULAR_UNSCALED_CHECK_SYNTAX_ICON = (short)18; /** * The paths to all the scaled images, relative to the root of the jar file. */ public static final String SCALED_IMAGE_PATHS[][] = { { "images/buttonEnterOff.jpg", "images/buttonEnterOn.jpg", "images/buttonInterruptOff.jpg", "images/buttonInterruptOn.jpg", "images/buttonLoadOff.jpg", "images/buttonLoadOn.jpg", "images/buttonLookOff.jpg", "images/buttonLookOn.jpg", "images/buttonResetOff.jpg", "images/buttonResetOn.jpg", "images/buttonRunOff.jpg", "images/buttonRunOn.jpg", "images/buttonStepOff.jpg", "images/buttonStepOn.jpg", "images/labelAddr.gif", "images/labelData.gif", "images/labelInstr.gif", "images/labelInwait.gif", "images/labelPc.gif", "images/labelReady.gif", "images/labelStdout.gif", "images/switchOff.jpg", "images/switchOn.jpg", "images/lightOff.jpg", "images/lightOn.jpg", "images/digitBlank.gif", "images/digit0.gif", "images/digit1.gif", "images/digit2.gif", "images/digit3.gif", "images/digit4.gif", "images/digit5.gif", "images/digit6.gif", "images/digit7.gif", "images/digit8.gif", "images/digit9.gif", "images/digitA.gif", "images/digitB.gif", "images/digitC.gif", "images/digitD.gif", "images/digitE.gif", "images/digitF.gif" }, { "images/punchcardDisabled.jpg", "images/punchcardUnselected.jpg", "images/punchcardSelected.jpg" } }; /** * The scale by which the raw images are loaded. * * @see #getImage(byte, int, short) */ public static final int ORIGINAL_IMAGE_SCALES[] = { 30, 20 }; /** * The maximum scale to which the images can be scaled. * * @see #getImage(byte, int, short) */ public static final int MAX_SCALE[] = { 60, 40 }; /** * The index of the machine scaled image group. */ public static final byte MACHINE_SCALED_IMAGE_GROUP = (byte)0; /** * The index of the unlit state of the Enter button. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_BUTTON_ENTER_OFF = (short)0; /** * The index of the lit state of the Enter button. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_BUTTON_ENTER_ON = (short)1; /** * The index of the unlit state of the Interrupt button. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_BUTTON_INTERRUPT_OFF = (short)2; /** * The index of the lit state of the Interrupt button. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_BUTTON_INTERRUPT_ON = (short)3; /** * The index of the unlit state of the Load button. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_BUTTON_LOAD_OFF = (short)4; /** * The index of the lit state of the Load button. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_BUTTON_LOAD_ON = (short)5; /** * The index of the unlit state of the Look button. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_BUTTON_LOOK_OFF = (short)6; /** * The index of the lit state of the Look button. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_BUTTON_LOOK_ON = (short)7; /** * The index of the unlit state of the Reset button. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_BUTTON_RESET_OFF = (short)8; /** * The index of the lit state of the Reset button. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_BUTTON_RESET_ON = (short)9; /** * The index of the unlit state of the Run button. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_BUTTON_RUN_OFF = (short)10; /** * The index of the lit state of the Run button. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_BUTTON_RUN_ON = (short)11; /** * The index of the unlit state of the Step button. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_BUTTON_STEP_OFF = (short)12; /** * The index of the lit state of the Step button. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_BUTTON_STEP_ON = (short)13; /** * The index of the "ADDR" label. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_LABEL_ADDR = (short)14; /** * The index of the "DATA" label. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_LABEL_DATA = (short)15; /** * The index of the "INSTR" label. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_LABEL_INSTR = (short)16; /** * The index of the "INWAIT" label. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_LABEL_INWAIT = (short)17; /** * The index of the "PC" label. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_LABEL_PC = (short)18; /** * The index of the "READY" label. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_LABEL_READY = (short)19; /** * The index of the "STDOUT" label. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_LABEL_STDOUT = (short)20; /** * The index of the down state of the switch. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_SWITCH_OFF = (short)21; /** * The index of the up state of the switch. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_SWITCH_ON = (short)22; /** * The index of the unlit state of the light. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_LIGHT_OFF = (short)23; /** * The index of the lit state of the light. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_LIGHT_ON = (short)24; /** * The index of the blank LCD digit. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_DIGIT_BLANK = (short)25; /** * The indices of the LCD Digits. * * @see #getImage(byte, int, short) */ public static final short MACHINE_SCALED_DIGITS[] = { (short)26, (short)27, (short)28, (short)29, (short)30, (short)31, (short)32, (short)33, (short)34, (short)35, (short)36, (short)37, (short)38, (short)39, (short)40, (short)41 }; /** * The index of the stdio scaled image group. */ public static final byte STDIO_SCALED_IMAGE_GROUP = (byte)1; /** * The index of the punch card in its disabled state. * * @see #getImage(byte, int, short) */ public static final short STDIO_SCALED_PUNCHCARD_DISABLED = (short)0; /** * The index of the punch card in its unselected state. * * @see #getImage(byte, int, short) */ public static final short STDIO_SCALED_PUNCHCARD_UNSELECTED = (short)1; /** * The index of the punch card in its selected state. * * @see #getImage(byte, int, short) */ public static final short STDIO_SCALED_PUNCHCARD_SELECTED = (short)2; private static final Toolkit TOOLKIT = Toolkit.getDefaultToolkit(); private static final TImageManager INSTANCE = new TImageManager(); private Image unscaledImageArray[][]; private Icon unscaledIconArray[][]; private boolean unscaledGroupDone[]; private Image scaledImageArray[][][]; private Icon scaledIconArray[][][]; private boolean scaledGroupDone[][]; /** * Constructs a ToyImageManager which will load its images from the JAR file. */ private TImageManager() { unscaledImageArray = new Image[UNSCALED_IMAGE_PATHS.length][]; unscaledIconArray = new Icon[UNSCALED_IMAGE_PATHS.length][]; unscaledGroupDone = new boolean[UNSCALED_IMAGE_PATHS.length]; scaledImageArray = new Image[SCALED_IMAGE_PATHS.length][][]; scaledIconArray = new Icon[SCALED_IMAGE_PATHS.length][][]; scaledGroupDone = new boolean[SCALED_IMAGE_PATHS.length][]; for (int ctr = 0; ctr < SCALED_IMAGE_PATHS.length; ctr++) { scaledImageArray[ctr] = new Image[MAX_SCALE[ctr] + 1][]; scaledIconArray[ctr] = new Icon[MAX_SCALE[ctr] + 1][]; scaledGroupDone[ctr] = new boolean[MAX_SCALE[ctr] + 1]; } } /** * Returns wheter or not a group of unscaled images has been prepared. Calling this method will * not cause the TImageManager to begin preparing the images. * * @param unscaledGroupId The id of the group in question. An invalid value will result in * an ArrayIndexOutOfBoundsException. * @return True iff the entire group of unscaled images has been prepared. * @see #STARTUP_UNSCALED_IMAGE_GROUP * @see #REGULAR_UNSCALED_IMAGE_GROUP */ public static boolean isPrepared(byte unscaledGroupId) { return INSTANCE.unscaledGroupDone[unscaledGroupId]; } /** * Returns wheter or not a group of scaled images has been prepared. Calling this method will * not cause the TImageManager to begin preparing the images. * * @param scaledGroupId The id of the group in question. An invalid value will result in * an ArrayIndexOutOfBoundsException. * @param scale The scale in question. An invalid value will result in an * ArrayIndexOutOfBoundsException. * @return True iff the entire group of scaled images has been prepared. * @see #STARTUP_UNSCALED_IMAGE_GROUP * @see #REGULAR_UNSCALED_IMAGE_GROUP */ public static boolean isPrepared(byte scaledGroupId, int scale) { return INSTANCE.scaledGroupDone[scaledGroupId][scale]; } /** * Returns only when a group of unscaled images has been prepared. This method is thread-safe. * * @param unscaledGroupId The id of the group in question. An invalid value will result in * an ArrayIndexOutOfBoundsException. */ public static void prepare(byte unscaledGroupId) { // Quick test if (INSTANCE.unscaledGroupDone[unscaledGroupId]) return; int length = 0; String pathArray[] = null; Image imageArray[] = null; Icon iconArray[] = null; // Check to see if the preparation will be our job synchronized (INSTANCE) { if (INSTANCE.unscaledImageArray[unscaledGroupId] == null) { pathArray = UNSCALED_IMAGE_PATHS[unscaledGroupId]; length = pathArray.length; INSTANCE.unscaledImageArray[unscaledGroupId] = imageArray = new Image[length]; INSTANCE.unscaledIconArray[unscaledGroupId] = iconArray = new Icon[length]; } } if (imageArray != null) { // If the preparation is our job, do it for (int ctr = 0; ctr < length; ctr++) { Image image = TResourceLoader.getImage(pathArray[ctr]); imageArray[ctr] = image; // The ImageIcon constructor will wait for the image to finish iconArray[ctr] = new ImageIcon(image); } INSTANCE.unscaledGroupDone[unscaledGroupId] = true; } else { // Otherwise, just wait while (!INSTANCE.unscaledGroupDone[unscaledGroupId]) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); return; } } } } /** * Returns only when a group of scaled images has been prepared. This method is thread-safe. * * @param scaledGroupId The id of the group in question. An invalid value will result in * an ArrayIndexOutOfBoundsException. * @param scale The desired scale of the image. An invalid value will result in an * ArrayIndexOutOfBoundsException. */ public static void prepare(byte scaledGroupId, int scale) { // Quick test if (INSTANCE.scaledGroupDone[scaledGroupId][scale]) return; int length = 0; String pathArray[] = null; Image imageArray[] = null; Icon iconArray[] = null; // Check to see if the preparation will be our job synchronized (INSTANCE) { if (INSTANCE.scaledImageArray[scaledGroupId][scale] == null) { pathArray = SCALED_IMAGE_PATHS[scaledGroupId]; length = pathArray.length; INSTANCE.scaledImageArray[scaledGroupId][scale] = imageArray = new Image[length]; INSTANCE.scaledIconArray[scaledGroupId][scale] = iconArray = new Icon[length]; } } if (imageArray != null) { // If the preparation is our job, do it // We need to do different things based on wheter or not the requested scale is the // original image scale int originalImageScale = ORIGINAL_IMAGE_SCALES[scaledGroupId]; if (scale != originalImageScale) { // We're doing a rescale job, first make sure that the original images are prepared prepare(scaledGroupId, originalImageScale); Image originalImageArray[] = INSTANCE.scaledImageArray[scaledGroupId][originalImageScale]; // Now prepare the scaled images for (int ctr = 0; ctr < length; ctr++) { int originalWidth = originalImageArray[ctr].getWidth(null); int originalHeight = originalImageArray[ctr].getHeight(null); Image image = originalImageArray[ctr].getScaledInstance( (int)(originalWidth * (double)scale / originalImageScale), (int)(originalHeight * (double)scale / originalImageScale), Image.SCALE_AREA_AVERAGING ); imageArray[ctr] = image; // The ImageIcon constructor will wait for the image to finish iconArray[ctr] = new ImageIcon(image); } } else { // We're just fetching the original images for (int ctr = 0; ctr < length; ctr++) { Image image = TResourceLoader.getImage(pathArray[ctr]); imageArray[ctr] = image; // The ImageIcon constructor will wait for the image to finish iconArray[ctr] = new ImageIcon(image); } } INSTANCE.scaledGroupDone[scaledGroupId][scale] = true; } else { // Otherwise, just wait while (!INSTANCE.scaledGroupDone[scaledGroupId][scale]) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); throw new RuntimeException("Prepare interrupted"); } } } } /** * Returns the requested unscaled image. If the group was not prepared yet, it will be * prepared in this thread. * * @param unscaledGroupId The id of the group to which the image belongs. An invalid value will * result in an ArrayIndexOutOfBoundsException. * @param index The index of the image in that group. An invalid value will result in an * ArrayIndexOutOfBoundsException. * @return The unscaled image at the given index of a given group. */ public static Image getImage(byte unscaledGroupId, short index) { prepare(unscaledGroupId); return INSTANCE.unscaledImageArray[unscaledGroupId][index]; } /** * Returns the requested unscaled image encapsulated in an icon. If the group was not prepared * yet, it will be prepared in this thread. * * @param unscaledGroupId The id of the group to which the image belongs. An invalid value will * result in an ArrayIndexOutOfBoundsException. * @param index The index of the image in that group. An invalid value will result in an * ArrayIndexOutOfBoundsException. * @return The unscaled image at the given index of a given group. */ public static Icon getIcon(byte unscaledGroupId, short index) { prepare(unscaledGroupId); return INSTANCE.unscaledIconArray[unscaledGroupId][index]; } /** * Returns the requested scaled image. If the group/scale was not prepared yet, it will be * prepared in this thread. * * @param scaledGroupId The id of the group to which the image belongs. An invalid value will * result in an ArrayIndexOutOfBoundsException. * @param scale The desired scale of the image. An invalid value will result in an * ArrayIndexOutOfBoundsException. * @param index The index of the image in that group. An invalid value will result in an * ArrayIndexOutOfBoundsException. * @return The unscaled image at the given index of a given group. */ public static Image getImage(byte scaledGroupId, int scale, short index) { prepare(scaledGroupId, scale); return INSTANCE.scaledImageArray[scaledGroupId][scale][index]; } /** * Returns the requested scaled image encapsulated in an icon. If the group/scale was not * prepared yet, it will be prepared in this thread. * * @param scaledGroupId The id of the group to which the image belongs. An invalid value will * result in an ArrayIndexOutOfBoundsException. * @param scale The desired scale of the image. An invalid value will result in an * ArrayIndexOutOfBoundsException. * @param index The index of the image in that group. An invalid value will result in an * ArrayIndexOutOfBoundsException. * @return The unscaled image at the given index of a given group. */ public static Icon getIcon(byte scaledGroupId, int scale, short index) { prepare(scaledGroupId, scale); return INSTANCE.scaledIconArray[scaledGroupId][scale][index]; } }