package net.hearthstats.game.ocr; import java.awt.image.BufferedImage; import java.io.File; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.imageio.ImageIO; import net.hearthstats.game.ScreenConfig; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Saves images on a background thread so that detection isn't held up by a slow filesystem. * * @author gtch */ public class BackgroundImageSave { private static final Logger debugLog = LoggerFactory.getLogger(BackgroundImageSave.class); private static final ExecutorService backgroundThreadExecutor = Executors.newSingleThreadExecutor(); private static String saveFolder; /** * Saves a PNG image in the temp/extraction folder, doing so on a background thread so that detection isn't held up. * * @param image The image to save. You should not make any further changes to this image on the main thread after * submitting it to this method, otherwise the save file will have unpredictable contents. * @param filename The name of the file to save. Do not include the extension (.png is added automatically) */ public static void savePngImage(final BufferedImage image, final String filename) { debugLog.debug("savePngImage({}, {})", image == null ? null : image.getWidth(), filename); if (image == null) { debugLog.warn("Could not save image {} because image is null", filename); return; } else if (StringUtils.isBlank(filename)) { debugLog.warn("Could not save image because filename is blank"); return; } backgroundThreadExecutor.submit(new Runnable() { @Override public void run() { try { File outputfile = new File((saveFolder == null ? filename : saveFolder + "/" + filename) + ".png"); ImageIO.write(image, "png", outputfile); debugLog.debug("Successfully saved image " + outputfile.getAbsolutePath()); } catch (Exception e) { debugLog.warn("Error saving image " + filename, e); } if (image != null) { image.flush(); } } }); } /** * <p>Saves a PNG image in the temp/extraction folder, cropped as specified. Ths image is saved on a background thread * so that detection isn't held up.</p> * <p>The code assumes that it has been given a full-size Hearthstone screenshot, and so the position of the crop * is adjusted to the relative position for the actual screen in the image. In other words, if the screenshot is * higher resolution or lower resolution than the reference 1600x1200 size then the crop is moved accordingly.</p> * * @param image The image to save. You should not make any further changes to this image on the main thread after * submitting it to this method, otherwise the save file will have unpredictable contents. * @param filename The name of the file to save. Do not include the extension (.png is added automatically) * @param x the X coordinate of the upper-left corner of the crop, relative to a 1600-pixel wide screen * @param y the Y coordinate of the upper-left corner of the crop, relative to a 1200-pixel high screen * @param w the width of the specified rectangular region, relative to a 1600-pixel wide screen * @param h the height of the specified rectangular region, relative to a 1200-pixel high screen */ public static void saveCroppedPngImage(final BufferedImage image, final String filename, int x, int y, int w, int h) { float ratio = ScreenConfig.getRatio(image); int xOffset = ScreenConfig.getXOffset(image, ratio); int relativeX = (int) (x * ratio + xOffset); int relativeY = (int) (y * ratio); int relativeWidth = (int) (w * ratio); int relativeHeight = (int) (h * ratio); debugLog.debug("Cropping image to x={} y={} width={} height={}", relativeX, relativeY, relativeWidth, relativeHeight); BufferedImage croppedImage = image.getSubimage(relativeX, relativeY, relativeWidth, relativeHeight); savePngImage(croppedImage, filename); } /** * Sets the location where images will be saved. * @param saveFolder A folder/directory path. */ public static void setSaveFolder(String saveFolder) { BackgroundImageSave.saveFolder = saveFolder; } }