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}