001package jmri.jmrit.etcs; 002 003import java.awt.Color; 004import java.awt.Image; 005import java.awt.image.BufferedImage; 006import java.io.IOException; 007 008import javax.annotation.CheckForNull; 009import javax.annotation.Nonnull; 010import javax.imageio.ImageIO; 011import javax.swing.ImageIcon; 012 013import jmri.jmrit.Sound; 014import jmri.util.FileUtil; 015import jmri.util.ThreadingUtil; 016 017import org.apiguardian.api.API; 018 019/** 020 * Class to locate ERTMS Graphical and Audio resources. 021 * @author Steve Young Copyright (C) 2024 022 */ 023@API(status=API.Status.EXPERIMENTAL) 024public class ResourceUtil { 025 026 private ResourceUtil(){} 027 028 private static final String RESOURCES_DIRECTORY = "resources" + FileUtil.SEPARATOR + "icons" + 029 FileUtil.SEPARATOR + "etcs" + FileUtil.SEPARATOR; 030 031 private static final String SOUNDS_DIR = "resources" + FileUtil.SEPARATOR + "sounds" + FileUtil.SEPARATOR; 032 033 private static final Color BACKGROUND_COLOUR = new Color(3,17,34); // dark blue background 034 035 private static boolean soundsInitialised = false; 036 037 private static Sound sound1; 038 private static Sound sound2; 039 private static Sound sound3; 040 private static Sound sound4; 041 042 /** 043 * Get the File for an Image. 044 * Note this File may not actually exist. 045 * @param fileName the Filename to search for. 046 * @return File with appropriate Directory. 047 */ 048 @Nonnull 049 public static java.io.File getImageFile(String fileName){ 050 return new java.io.File(getNameAndDirectoryForFile(fileName)); 051 } 052 053 // Limited Supervsion MO_21 handled separately < ERTMS4 054 private static String getNameAndDirectoryForFile(@Nonnull String fileName){ 055 return FileUtil.getExternalFilename(RESOURCES_DIRECTORY + fileName); 056 } 057 058 /** 059 * Get the Image Icon for a given FileName. 060 * @param fileName the FileName to search for. 061 * @return ImageIcon, or null if image not located. 062 */ 063 @CheckForNull 064 public static ImageIcon getImageIcon(String fileName){ 065 String externalFileName = getNameAndDirectoryForFile(fileName); 066 try { 067 java.io.File imageFile = new java.io.File(externalFileName); 068 if(!imageFile.exists()) { 069 log.error("Image file {} not found!", externalFileName); 070 return null; 071 } 072 Image image = ImageIO.read(imageFile); 073 return new ImageIcon(image); 074 } catch (IOException ex){ 075 log.error("IO exception: ", ex); 076 return null; 077 } 078 } 079 080 @CheckForNull 081 public static BufferedImage getTransparentImage(@Nonnull String str) { 082 if (str.isBlank()){ 083 return null; 084 } 085 BufferedImage a = readFile(ResourceUtil.getImageFile(str)); 086 return ResourceUtil.convertColorToTransparent(a); 087 } 088 089 @CheckForNull 090 public static BufferedImage readFile(java.io.File f){ 091 BufferedImage a = null; 092 try { 093 a = ImageIO.read(f); 094 } catch (IOException ex) { 095 log.error("Exception while reading image {}", f,ex); 096 } 097 return a; 098 } 099 100 /** 101 * Convert an image containing the DMI Background Colour to a transparent background. 102 * @param image the Image to convert. 103 * @return converted image, or null. 104 */ 105 @CheckForNull 106 public static BufferedImage convertColorToTransparent(@CheckForNull BufferedImage image) { 107 if ( image == null ) { 108 return null; 109 } 110 int width = image.getWidth(); 111 int height = image.getHeight(); 112 BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 113 Color targetColor = BACKGROUND_COLOUR; 114 115 for (int y = 0; y < height; y++) { 116 for (int x = 0; x < width; x++) { 117 // Get the color of the current pixel 118 Color pixelColor = new Color(image.getRGB(x, y), true); 119 120 // Check if the pixel color matches the target color 121 if (pixelColor.equals(targetColor)) { 122 // Set the alpha (transparency) value to 0 123 pixelColor = new Color(0, 0, 0, 0); 124 } 125 126 // Set the modified color in the new image 127 newImage.setRGB(x, y, pixelColor.getRGB()); 128 } 129 } 130 return newImage; 131 } 132 133 /** 134 * Play one of the DMI UI Sounds. 135 * 1 - S1_toofast.wav - 2 secs 136 * 2 - S2_warning.wav - 3 secs 137 * 3 - S_info.wav - 1 sec 138 * 4 - click.wav - 1 sec 139 * @param sound which Sound, plays once. 140 */ 141 public static void playDmiSound(int sound) throws IllegalArgumentException { 142 143 if ( !soundsInitialised ) { 144 soundsInitialised = true; 145 146 sound1 = new Sound(FileUtil.getExternalFilename(SOUNDS_DIR + "S1_toofast.wav")); 147 sound2 = new Sound(FileUtil.getExternalFilename(SOUNDS_DIR + "S2_warning.wav")); 148 sound3 = new Sound(FileUtil.getExternalFilename(SOUNDS_DIR + "S_info.wav")); 149 sound4 = new Sound(FileUtil.getExternalFilename(SOUNDS_DIR + "click.wav")); 150 151 sound1.setAutoClose(false); 152 sound2.setAutoClose(false); 153 sound3.setAutoClose(false); 154 sound4.setAutoClose(false); 155 } 156 157 Sound s; 158 switch (sound) { 159 case 1: 160 s = sound1; 161 break; 162 case 2: 163 startSound(sound2, true); 164 return; 165 case 3: 166 s = sound3; 167 break; 168 case 4: 169 s = sound4; 170 break; 171 default: 172 throw new IllegalArgumentException("No Sound for slot "+ sound); 173 } 174 startSound(s, false); 175 } 176 177 private static void startSound(Sound s, boolean loop) { 178 Thread t = ThreadingUtil.newThread( ( loop ? s::loop : s::play), "DMI Sound " + s ); 179 t.setPriority(Thread.MAX_PRIORITY); 180 ThreadingUtil.runOnGUI(t::start); 181 } 182 183 /** 184 * Stop a DMI Sound from playing. 185 * @param sound normally 2, the only sound which plays in a loop. 186 */ 187 public static void stopDmiSound(int sound) { 188 if ( sound == 2 ) { 189 sound2.stop(); 190 } 191 } 192 193 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ResourceUtil.class); 194 195}