org.sikuli.script.Image Maven / Gradle / Ivy
/*
* Copyright (c) 2010-2016, Sikuli.org, sikulix.com
* Released under the MIT License.
*
*/
package org.sikuli.script;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.io.File;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.imgproc.Imgproc;
import org.sikuli.basics.Debug;
import org.sikuli.basics.FileManager;
import org.sikuli.basics.Settings;
import org.sikuli.natives.Vision;
/**
* This class hides the complexity behind image names given as string.
*
Image does not have public nor protected constructors: use create()
*
It's companion is {@link ImagePath} that maintains a list of places, where image files are
* loaded from.
* Another companion {@link ImageGroup} will allow to look at images in a folder as a
* group.
* An Image object:
* - has a name, either given or taken from the basename
* - keeps it's in memory buffered image in a configurable cache avoiding reload
* from source
* - remembers, where it was found when searched the last time
* - can be sourced from the filesystem, from jars, from the web and from other
* in memory images
* - will have features for basic image manipulation and presentation
* - contains the stuff to communicate with the underlying OpenCV based search
* engine
*
* This class maintains
* - a list of all images ever loaded in this session with their source
* reference and a ref to the image object
* - a list of all images currently having their content in memory (buffered
* image) (managed as a configurable cache)
* The caching can be configured using {@link Settings#setImageCache(int)}
*/
public class Image {
static RunTime runTime = RunTime.get();
private static String me = "Image: ";
private static int lvl = 3;
private static void log(int level, String message, Object... args) {
Debug.logx(level, me + message, args);
}
private static List images = Collections.synchronizedList(new ArrayList());
private static Map imageFiles = Collections.synchronizedMap(new HashMap());
private static Map imageNames = Collections.synchronizedMap(new HashMap());
private static final int KB = 1024;
private static final int MB = KB * KB;
private final static String isBImg = "__BufferedImage__";
private static long currentMemory = 0;
private static synchronized long currentMemoryChange(long size, long max) {
long maxMemory = max;
if (max < 0) {
maxMemory = Settings.getImageCache() * MB;
currentMemory += size;
}
if (currentMemory > maxMemory) {
Image first;
while (images.size() > 0 && currentMemory > maxMemory) {
first = images.remove(0);
first.bimg = null;
currentMemory -= first.bsize;
}
if (maxMemory == 0) {
currentMemory = 0;
} else {
currentMemory = Math.max(0, currentMemory);
}
}
if (size < 0) {
currentMemory = Math.max(0, currentMemory);
}
return currentMemory;
}
private static long currentMemoryUp(long size) {
return currentMemoryChange(size, -1);
}
private static long currentMemoryDown(long size) {
currentMemory -= size;
currentMemory = Math.max(0, currentMemory);
return currentMemoryChange(-size, -1);
}
private static long currentMemoryDownUp(int sizeOld, int sizeNew) {
currentMemoryDown(sizeOld);
return currentMemoryUp(sizeNew);
}
private static boolean isCaching() {
return Settings.getImageCache() > 0;
}
public static void clearCache(int maxSize) {
currentMemoryChange(0, maxSize);
}
public static void reload(String fpImage) {
// URL uImage = FileManager.makeURL(fpImage);
URL uImage = imageNames.get(fpImage);
if (imageFiles.containsKey(uImage)) {
Image image = imageFiles.get(uImage);
int sizeOld = image.bsize;
if (null != image.loadAgain()) {
currentMemoryDownUp(sizeOld, image.bsize);
image.setLastSeen(null, 0);
}
}
}
private static boolean ideShouldReload = false;
protected boolean wasRecaptured = false;
public static void setIDEshouldReload(Image img) {
ideShouldReload = true;
img.wasRecaptured = true;
img.lastSeen = null;
}
public static boolean getIDEshouldReload() {
boolean state = ideShouldReload;
ideShouldReload = false;
return state;
}
public boolean isRecaptured() {
boolean state = wasRecaptured;
wasRecaptured = false;
return state;
}
//
private String imageName = null;
private String imageNameGiven = null;
private boolean bHasIOException = false;
public boolean hasIOException() {
return bHasIOException;
}
public void setHasIOException(boolean state) {
bHasIOException = state;
}
public String getImageName() {
return imageName;
}
public Image setImageName(String imageName) {
this.imageName = imageName;
return this;
}
//
//
private URL fileURL = null;
private String imageAsFile = null;
public URL getFileURL() {
return fileURL;
}
public Image setFileURL(URL fileURL) {
this.fileURL = fileURL;
return this;
}
//
//
private BufferedImage bimg = null;
protected Image setBimg(BufferedImage bimg) {
this.bimg = bimg;
if (bimg != null) {
bwidth = bimg.getWidth();
bheight = bimg.getHeight();
bsize = bimg.getData().getDataBuffer().getSize();
} else {
bsize = 0;
bwidth = -1;
bheight = -1;
}
return this;
}
private int bsize = 0;
private int bwidth = -1;
private int bheight = -1;
//
private ImageGroup group = null;
//
private boolean imageIsText = false;
/**
*
* @return true if the given image name did not give a valid image so it might
* be text to search
*/
public boolean isText() {
return imageIsText;
}
private Image setIsText(boolean val) {
imageIsText = val;
return this;
}
public String getText() {
return imageNameGiven;
}
//
//
private boolean imageIsAbsolute = false;
/**
*
* @return true if image was given with absolute filepath
*/
public boolean isAbsolute() {
return imageIsAbsolute;
}
public Image setIsAbsolute(boolean val) {
imageIsAbsolute = val;
return this;
}
//
//
private boolean imageIsBundled = false;
private Image setIsBundled(boolean imageIsBundled) {
this.imageIsBundled = imageIsBundled;
return this;
}
/**
* INTERNAL USE: image is contained in a bundle (.sikuli)
* @return true/false
*/
public boolean isBundled() {
return imageIsBundled;
}
//
//
private boolean imageIsPattern = false;
/**
* true if this image contains pattern aspects
* only useable with the new ImageFinder
* @return true if yes, false otherwise
*/
public boolean isPattern() {
return imageIsPattern;
}
public Image setIsPattern(boolean imageIsPattern) {
this.imageIsPattern = imageIsPattern;
return this;
}
//
//
private int waitAfter;
/**
* Get the value of waitAfter
*
* @return the value of waitAfter
*/
public int getWaitAfter() {
return waitAfter;
}
/**
* Set the value of waitAfter
*
* @param waitAfter new value of waitAfter
* @return the image
*/
public Image setWaitAfter(int waitAfter) {
this.waitAfter = waitAfter;
return this;
}
//
//
private Location offset = new Location(0, 0);
/**
* Get the value of offset
*
* @return the value of offset
*/
public Location getOffset() {
return offset;
}
/**
* Set the value of offset
*
* @param offset new value of offset
* @return the image
*/
public Image setOffset(Location offset) {
this.offset = offset;
return this;
}
//
//
private float similarity = (float) Settings.MinSimilarity;
/**
* Get the value of similarity
*
* @return the value of similarity
*/
public float getSimilarity() {
return similarity;
}
/**
* Set the value of similarity
*
* @param similarity new value of similarity
* @return the image
*/
public Image setSimilarity(float similarity) {
this.similarity = similarity;
return this;
}
//
//
private Rectangle lastSeen = null;
private double lastScore = 0.0;
/**
* if the image was already found before
*
* @return the rectangle where it was found
*/
public Rectangle getLastSeen() {
return lastSeen;
}
/**
* if the image was already found before
*
* @return the similarity score
*/
public double getLastSeenScore() {
return lastScore;
}
/**
* Internal Use: set the last seen info after a find
*
* @param lastSeen Match
* @param sim SimilarityScore
* @return the image
*/
protected Image setLastSeen(Rectangle lastSeen, double sim) {
this.lastSeen = lastSeen;
this.lastScore = sim;
if (group != null) {
group.addImageFacts(this, lastSeen, sim);
}
return this;
}
//
private boolean beSilent = false;
/**
* to support a raster over the image
*/
private int rows = 0;
private int cols = 0;
private int rowH = 0;
private int colW = 0;
private int rowHd = 0;
private int colWd = 0;
@Override
public String toString() {
return String.format(
(imageName != null ? imageName : "__UNKNOWN__") + ": (%dx%d)", bwidth, bheight)
+ (lastSeen == null ? ""
: String.format(" seen at (%d, %d) with %.2f", lastSeen.x, lastSeen.y, lastScore));
}
private Image() {
}
private Image(String fname, URL fURL) {
init(fname, fURL, true);
}
private Image(String fname, URL fURL, boolean silent) {
init(fname, fURL, silent);
}
private void init(String fileName, URL fURL, boolean silent) {
imageName = fileName;
if (imageName.isEmpty() || fURL == null) {
return;
}
fileURL = fURL;
if (ImagePath.isImageBundled(fURL)) {
imageIsBundled = true;
imageName = new File(imageName).getName();
}
beSilent = silent;
load();
}
private BufferedImage load() {
BufferedImage bImage = null;
if (fileURL != null) {
bimg = null;
try {
bImage = ImageIO.read(fileURL);
} catch (Exception e) {
if (!beSilent) {
log(-1, "could not be loaded: %s", fileURL);
}
bHasIOException = true;
fileURL = null;
return null;
}
if (imageName != null) {
imageFiles.put(fileURL, this);
imageNames.put(imageName, fileURL);
bwidth = bImage.getWidth();
bheight = bImage.getHeight();
bsize = bImage.getData().getDataBuffer().getSize();
log(lvl, "loaded: %s (%s)", imageName, fileURL);
if (isCaching()) {
int maxMemory = Settings.getImageCache() * MB;
currentMemoryUp(bsize);
bimg = bImage;
images.add(this);
log(lvl, "cached: %s (%d KB) (# %d KB %d -- %d %% of %d MB)",
imageName, getKB(),
images.size(), (int) (currentMemory / KB),
(int) (100 * currentMemory / maxMemory), (int) (maxMemory / MB));
}
} else {
log(-1, "invalid! not loaded! %s", fileURL);
}
}
return bImage;
}
private BufferedImage loadAgain() {
BufferedImage bImage = null;
if (fileURL != null) {
bimg = null;
try {
bImage = ImageIO.read(fileURL);
} catch (Exception e) {
if (!beSilent) {
log(-1, "could not be loaded again: %s", fileURL);
}
bHasIOException = true;
imageFiles.remove(fileURL);
return null;
}
imageFiles.put(fileURL, this);
imageNames.put(imageName, fileURL);
bwidth = bImage.getWidth();
bheight = bImage.getHeight();
bsize = bImage.getData().getDataBuffer().getSize();
log(lvl, "loaded again: %s (%s)", imageName, fileURL);
}
return bImage;
}
private Image copy() {
Image imgTarget = new Image();
imgTarget.setImageName(imageName);
imgTarget.setFileURL(fileURL);
imgTarget.setBimg(bimg);
imgTarget.setGroup(group);
imgTarget.setIsAbsolute(imageIsAbsolute);
imgTarget.setIsText(imageIsText);
imgTarget.setIsBundled(imageIsBundled);
imgTarget.setLastSeen(getLastSeen(), getLastSeenScore());
imgTarget.setHasIOException(hasIOException());
if (isPattern()) {
imgTarget.setSimilarity(similarity);
imgTarget.setOffset(offset);
imgTarget.setWaitAfter(waitAfter);
imgTarget.setIsPattern(true);
}
return imgTarget;
}
/**
* create a new Image as copy of the given Image
* @param imgSrc given Image
* @return new Image
*/
public static Image create(Image imgSrc) {
return imgSrc.copy();
}
/**
* create a new image from a filename
* file ending .png is added if missing (currently valid: png, jpg, jpeg)
* relative filename: [...path.../]name[.png] is searched on current image path
* absolute filename is taken as is
* if image exists, it is loaded to cache
* already loaded image with same name (given path) is reused (taken from cache)
*
* if image not found, it might be a text to be searched (imageIsText = true)
*
* @param fName image filename
* @return an Image object (might not be valid - check with isValid())
*/
public static Image create(String fName) {
Image img = get(fName, false);
return createImageValidate(img, true);
}
/**
* create a new Image with Pattern aspects from an existing Pattern
* @param p a Pattern
* @return the new Image
*/
public static Image create(Pattern p) {
Image img = p.getImage().copy();
img.setIsPattern(true);
img.setSimilarity(p.getSimilar());
img.setOffset(p.getTargetOffset());
img.setWaitAfter(p.getTimeAfter());
return img;
}
/**
* create a new image from the given url
* file ending .png is added if missing
* filename: ...url-path.../name[.png] is loaded from the url and and cached
*
* already loaded image with same url is reused (reference) and taken from
* cache
*
* @param url image file URL
* @return the image
*/
public static Image create(URL url) {
Image img = get(url);
if (img == null) {
img = new Image(url);
}
return createImageValidate(img, true);
}
protected static Image getImageFromTarget(PSI target) {
if (target instanceof Pattern) {
return ((Pattern) target).getImage();
} else if (target instanceof String) {
Image img = get((String) target, true);
img = createImageValidate(img, true);
return img;
} else if (target instanceof Image) {
return (Image) target;
} else {
runTime.abortScripting("aborting script at:",
String.format("find, wait, exists: invalid parameter: %s", target));
}
return null;
}
/**
* FOR INTERNAL USE: from IDE - suppresses load error message
*
* @param fName image filename
* @return this
*/
public static Image createThumbNail(String fName) {
Image img = get(fName, true);
return createImageValidate(img, false);
}
private static Image createImageValidate(Image img, boolean verbose) {
if (img == null) {
log(-1, "Image not valid, creating empty Image");
return new Image("", null);
}
if (!img.isValid()) {
if (Settings.OcrTextSearch) {
img.setIsText(true);
if (Settings.isValidImageFilename(img.getName())) {
img.setIsText(false);
}
} else {
if (verbose) {
log(-1, "Image not valid, but TextSearch is switched off!");
}
}
}
return img;
}
/**
* stores the image as PNG file in the standard temp folder
* with a created filename (sikuli-image-#unique-random#.png)
* if not yet stored before
*
* @return absolute path to stored file
*/
public String asFile() {
if (imageAsFile == null) {
if (bimg != null) {
imageAsFile = FileManager.saveTmpImage(bimg);
}
}
return imageAsFile;
}
/**
* FOR INTERNAL USE: see get(String, boolean)
*
* @param fName image filename
* @return this
*/
protected static Image get(String fName) {
return get(fName, false);
}
/**
* FOR INTERNAL USE: tries to get the image from the cache, if not cached yet:
* create and load a new image
*
* @param fName image filename
* @param silent true: suppress some error messages
* @return this
*/
private static Image get(String fName, boolean silent) {
if (fName == null || fName.isEmpty()) {
return null;
}
Image img = null;
if (fName.startsWith("\t") && fName.endsWith("\t")) {
fName = fName.substring(1, fName.length() - 1);
img = new Image();
img.setIsText(true);
} else {
fName = FileManager.slashify(fName, false);
URL fURL = null;
String fileName = Settings.getValidImageFilename(fName);
if (fileName.isEmpty()) {
log(-1, "not a valid image type: " + fName);
fileName = fName;
}
File imgFile = new File(fileName);
if (imgFile.isAbsolute()) {
if (imgFile.exists()) {
fURL = FileManager.makeURL(fileName);
}
} else {
fURL = imageNames.get(fileName);
if (fURL == null) {
fURL = ImagePath.find(fileName);
}
}
if (fURL != null) {
img = imageFiles.get(fURL);
if (img != null && null == imageNames.get(img.imageName)) {
imageNames.put(img.imageName, fURL);
}
}
if (img == null) {
img = new Image(fileName, fURL, silent);
img.setIsAbsolute(imgFile.isAbsolute());
} else {
if (img.bimg != null) {
log(3, "reused: %s (%s)", img.imageName, img.fileURL);
} else {
if (Settings.getImageCache() > 0) {
img.load();
}
}
}
}
img.imageNameGiven = fName;
return img;
}
protected static void set(Image img) {
URL fURL = null;
File imgFile = new File(img.getName());
if (imgFile.isAbsolute()) {
if (imgFile.exists()) {
fURL = FileManager.makeURL(img.getName());
}
} else {
fURL = imageNames.get(img.getName());
if (fURL == null) {
fURL = ImagePath.find(img.getName());
}
}
if (fURL != null) {
img.init(img.getName(), fURL, true);
}
}
protected static Image get(URL imgURL) {
return imageFiles.get(imgURL);
}
private Image(URL fURL) {
if ("file".equals(fURL.getProtocol())) {
init(fURL.getPath(), fURL, true);
} else {
init(getNameFromURL(fURL), fURL, true);
}
}
private static String getNameFromURL(URL fURL) {
//TODO add handling for http
if ("jar".equals(fURL.getProtocol())) {
int n = fURL.getPath().lastIndexOf(".jar!/");
int k = fURL.getPath().substring(0, n).lastIndexOf("/");
if (n > -1) {
return "JAR:" + fURL.getPath().substring(k + 1, n) + fURL.getPath().substring(n + 5);
}
}
return "???:" + fURL.getPath();
}
/**
* create a new image from a buffered image
* can only be reused with the object reference
*
* @param img BufferedImage
*/
public Image(BufferedImage img) {
this(img, null);
}
/**
* create a new image from a buffered image
* giving it a descriptive name for printout and logging
* can only be reused with the object reference
*
* @param img BufferedImage
* @param name descriptive name
*/
public Image(BufferedImage img, String name) {
imageName = isBImg;
if (name != null) {
imageName += name;
}
bimg = img;
bwidth = bimg.getWidth();
bheight = bimg.getHeight();
log(lvl, "BufferedImage: (%d, %d)%s", bwidth, bheight,
(name == null ? "" : " with name: " + name));
}
/**
* create a new image from a Sikuli ScreenImage (captured)
* can only be reused with the object reference
*
* @param img ScreenImage
*/
public Image(ScreenImage img) {
this(img.getImage(), null);
}
/**
* create a new image from a Sikuli ScreenImage (captured)
* giving it a descriptive name for printout and logging
* can only be reused with the object reference
*
* @param img ScreenImage
* @param name descriptive name
*/
public Image(ScreenImage img, String name) {
this(img.getImage(), name);
}
/**
* INTERNAL USE: IDE: to get rid of cache entries at script save, close or
* save as
*
* @param bundlePath absolute path for an image set in this folder
*/
public static void purge(String bundlePath) {
if (imageFiles.isEmpty() || ImagePath.getPaths().get(0) == null) {
return;
}
URL pathURL = FileManager.makeURL(bundlePath);
if (!ImagePath.getPaths().get(0).pathURL.equals(pathURL)) {
log(-1, "purge: not current bundlepath: " + pathURL);
return;
}
purge(pathURL);
}
protected static void purge(ImagePath.PathEntry path) {
if (path == null) {
return;
}
purge(path.pathURL);
}
protected static synchronized void purge(URL pathURL) {
List imagePurgeList = new ArrayList<>();
List imageNamePurgeList = new ArrayList<>();
URL imgURL;
Image img;
log(lvl, "purge: ImagePath: %s", pathURL.getPath());
Iterator> it = imageFiles.entrySet().iterator();
Map.Entry entry;
while (it.hasNext()) {
entry = it.next();
imgURL = entry.getKey();
if (imgURL.toString().startsWith(pathURL.toString())) {
log(lvl + 1, "purge: URL: %s", imgURL.toString());
img = entry.getValue();
imagePurgeList.add(img);
imageNamePurgeList.add(img.imageName);
it.remove();
}
}
if (!imagePurgeList.isEmpty()) {
Iterator bit = images.iterator();
while (bit.hasNext()) {
img = bit.next();
if (imagePurgeList.contains(img)) {
bit.remove();
log(lvl + 1, "purge: bimg: %s", img);
currentMemoryDown(img.bsize);
}
}
}
for (String name : imageNamePurgeList) {
imageNames.remove(name);
}
}
public boolean isFile() {
if (isValid()) {
URL furl = getURL();
if ("file".equals(furl.getProtocol())) {
return true;
}
}
return false;
}
public File remove() {
URL furl = null;
if (isFile()) {
furl = getURL();
unCacheImage(furl);
return new File(furl.getPath());
}
return null;
}
public void delete() {
File fImg = remove();
if (null != fImg) FileManager.deleteFileOrFolder(fImg);
}
private String hasBackup = "";
protected boolean backup() {
if (isValid()) {
File fOrg = new File(fileURL.getPath());
File fBack = new File(fOrg.getParentFile(), "_BACKUP_" + fOrg.getName());
if (FileManager.xcopy(fOrg, fBack)) {
hasBackup = fBack.getPath();
log(lvl, "backup: %s created", fBack.getName());
return true;
}
log(-1, "backup: %s did not work", fBack.getName());
}
return false;
}
protected boolean restore() {
if (!hasBackup.isEmpty()) {
File fBack = new File(hasBackup);
File fOrg = new File(hasBackup.replace("_BACKUP_", ""));
if (FileManager.xcopy(fBack, fOrg)) {
log(lvl, "restore: %s restored", fOrg.getName());
FileManager.deleteFileOrFolder(fBack);
hasBackup = "";
return true;
}
log(-1, "restore: %s did not work", fBack.getName());
}
return false;
}
/**
* purge the given image file's in memory image data and remove it from cache.
* @param imgFileName an absolute filename
*/
public static void unCacheBundledImage(String imgFileName) {
URL imgURL = FileManager.makeURL(new File(imgFileName).getAbsolutePath());
unCacheImage(imgURL);
}
/**
* purge the given image's in memory image data and remove it from cache.
* @param imgURL URL of an image file
*/
public static void unCacheImage(URL imgURL) {
Image img = imageFiles.get(imgURL);
if (img == null) {
return;
}
currentMemoryDown(img.bsize);
img.setBimg(null);
images.remove(img);
}
/**
* Print the current state of the cache
*/
public static void dump() {
dump(0);
}
/**
* Print the current state of the cache, verbosity depends on debug level
* @param lvl debug level used here
*/
public static void dump(int lvl) {
log(lvl, "--- start of Image dump ---");
ImagePath.dump(lvl);
log(lvl, "ImageFiles entries: %d", imageFiles.size());
Iterator> it = imageFiles.entrySet().iterator();
Map.Entry entry;
while (it.hasNext()) {
entry = it.next();
log(lvl, entry.getKey().toString());
}
log(lvl, "ImageNames entries: %d", imageNames.size());
Iterator> nit = imageNames.entrySet().iterator();
Map.Entry name;
while (nit.hasNext()) {
name = nit.next();
log(lvl, "%s %d KB (%s)", new File(name.getKey()).getName(),
imageFiles.get(name.getValue()).getKB(), name.getValue());
}
if (Settings.getImageCache() == 0) {
log(lvl, "Cache state: switched off!");
} else {
log(lvl, "Cache state: Max %d MB (entries: %d used: %d %% %d KB)",
Settings.getImageCache(), images.size(),
(int) (100 * currentMemory / (Settings.getImageCache() * MB)), (int) (currentMemory / KB));
}
log(lvl, "--- end of Image dump ---");
}
/**
* clears all caches (should only be needed for debugging)
*/
public static void reset() {
clearCache(0);
imageNames.clear();
imageFiles.clear();
}
/**
* Get the image's descriptive name
*
* @return the name
*/
public String getName() {
if (isText()) {
return imageNameGiven;
}
return imageName;
}
/**
*
* @return the current ImageGroup
*/
public ImageGroup getGroup() {
return group;
}
/**
* set the ImageGroup this image should belong to
*
* @param group ImageGroup
*/
public void setGroup(ImageGroup group) {
this.group = group;
}
/**
* check whether image is available for Finder.find()
* This is for backward compatibility
* The new ImageFinder uses isUsable()
*
* @return true if lodable from file or is an in memory image
*/
public boolean isValid() {
return fileURL != null || getName().contains(isBImg);
}
/**
* checks, wether the Image can be used with the new ImageFinder
* @return true/false
*/
public boolean isUseable() {
return isValid() || imageIsPattern;
}
/**
*
* @return the evaluated url for this image (might be null)
*/
public URL getURL() {
return fileURL;
}
/**
* @return the image's absolute filename or null if jar, http or in memory
* image
*/
public String getFilename() {
if (fileURL != null && "file".equals(fileURL.getProtocol())) {
return new File(fileURL.getPath()).getAbsolutePath();
} else {
return imageName;
}
}
/**
* return the image's BufferedImage (load it if not in cache)
*
* @return BufferedImage (might be null)
*/
public BufferedImage get() {
return get(true);
}
protected BufferedImage get(boolean shouldLoad) {
if (bimg != null) {
if (fileURL == null) {
log(lvl + 1, "getImage inMemory: %s", imageName);
} else {
log(lvl + 1, "getImage from cache: %s", imageName);
}
return bimg;
} else {
if (shouldLoad) {
return load();
} else {
return null;
}
}
}
/**
*
* @return size of image
*/
public Dimension getSize() {
return new Dimension(bwidth, bheight);
}
private int getKB() {
if (bimg == null) {
return 0;
}
return (int) bsize / KB;
}
/**
* resize the loaded image with factor using Graphics2D.drawImage
* @param factor resize factor
* @return a new BufferedImage resized (width*factor, height*factor)
*/
public BufferedImage resize(float factor) {
int type;
BufferedImage bufimg = get();
type = bufimg.getType() == 0 ? BufferedImage.TYPE_INT_ARGB : bufimg.getType();
int width = (int) (getSize().getWidth() * factor);
int height = (int) (getSize().getHeight() * factor);
BufferedImage resizedImage = new BufferedImage(width, height, type);
Graphics2D g = resizedImage.createGraphics();
g.drawImage(bufimg, 0, 0, width, height, null);
g.dispose();
return resizedImage;
}
/**
* create a sub image from this image
*
* @param x pixel column
* @param y pixel row
* @param w width
* @param h height
* @return the new image
*/
public Image getSub(int x, int y, int w, int h) {
BufferedImage bi = createBufferedImage(w, h);
Graphics2D g = bi.createGraphics();
g.drawImage(get().getSubimage(x, y, w, h), 0, 0, null);
g.dispose();
return new Image(bi);
}
/**
* create a sub image from this image
*
* @param part (the constants Region.XXX as used with {@link Region#get(int)})
* @return the sub image
*/
public Image getSub(int part) {
Rectangle r = Region.getRectangle(new Rectangle(0, 0, getSize().width, getSize().height), part);
return getSub(r.x, r.y, r.width, r.height);
}
/**
* store info: this image is divided vertically into n even rows
* a preparation for using getRow()
*
* @param n number of rows
* @return the top row
*/
public Image setRows(int n) {
return setRaster(n, 0);
}
/**
* store info: this image is divided horizontally into n even columns
* a preparation for using getCol()
*
* @param n number of Columns
* @return the leftmost column
*/
public Image setCols(int n) {
return setRaster(0, n);
}
/**
*
* @return number of eventually defined rows in this image or 0
*/
public int getRows() {
return rows;
}
/**
*
* @return height of eventually defined rows in this image or 0
*/
public int getRowH() {
return rowH;
}
/**
*
* @return number of eventually defined columns in this image or 0
*/
public int getCols() {
return cols;
}
/**
*
* @return width of eventually defined columns in this image or 0
*/
public int getColW() {
return colW;
}
/**
* store info: this image is divided into a raster of even cells
* a preparation for using getCell()
*
* @param r number of rows
* @param c number of columns
* @return the top left cell
*/
public Image setRaster(int r, int c) {
rows = r;
cols = c;
if (r > 0) {
rowH = (int) (getSize().height / r);
rowHd = getSize().height - r * rowH;
}
if (c > 0) {
colW = (int) (getSize().width / c);
colWd = getSize().width - c * colW;
}
return getCell(0, 0);
}
/**
* get the specified row counting from 0, if rows or raster are setup
negative
* counts reverse from the end (last = -1)
values outside range are 0 or last
* respectively
*
* @param r row number
* @return the row as new image or the image itself, if no rows are setup
*/
public Image getRow(int r) {
if (rows == 0) {
return this;
}
if (r < 0) {
r = rows + r;
}
r = Math.max(0, r);
r = Math.min(r, rows - 1);
return getSub(0, r * rowH, getSize().width, rowH);
}
/**
* get the specified column counting from 0, if columns or raster are setup
* negative counts reverse from the end (last = -1)
values outside range are 0
* or last respectively
*
* @param c column number
* @return the column as new image or the image itself, if no columns are
* setup
*/
public Image getCol(int c) {
if (cols == 0) {
return this;
}
if (c < 0) {
c = cols + c;
}
c = Math.max(0, c);
c = Math.min(c, cols - 1);
return getSub(c * colW, 0, colW, getSize().height);
}
/**
* get the specified cell counting from (0, 0), if a raster is setup
* negative counts reverse from the end (last = -1)
values outside range are 0
* or last respectively
*
* @param r row number
* @param c column number
* @return the cell as new image or the image itself, if no raster is setup
*/
public Image getCell(int r, int c) {
if (rows == 0) {
return getCol(c);
}
if (cols == 0) {
return getRow(r);
}
if (rows == 0 && cols == 0) {
return this;
}
if (r < 0) {
r = rows - r;
}
if (c < 0) {
c = cols - c;
}
r = Math.max(0, r);
r = Math.min(r, rows - 1);
c = Math.max(0, c);
c = Math.min(c, cols - 1);
return getSub(c * colW, r * rowH, colW, rowH);
}
/**
* get the OpenCV Mat version of the image's BufferedImage
*
* @return OpenCV Mat
*/
public Mat getMat() {
return createMat(get());
}
protected static Mat createMat(BufferedImage img) {
if (img != null) {
Debug timer = Debug.startTimer("Mat create\t (%d x %d) from \n%s", img.getWidth(), img.getHeight(), img);
Mat mat_ref = new Mat(img.getHeight(), img.getWidth(), CvType.CV_8UC4);
timer.lap("init");
byte[] data;
BufferedImage cvImg;
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
int[] nBits = {8, 8, 8, 8};
ColorModel cm = new ComponentColorModel(cs, nBits, true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
SampleModel sm = cm.createCompatibleSampleModel(img.getWidth(), img.getHeight());
DataBufferByte db = new DataBufferByte(img.getWidth() * img.getHeight() * 4);
WritableRaster r = WritableRaster.createWritableRaster(sm, db, new Point(0, 0));
cvImg = new BufferedImage(cm, r, false, null);
timer.lap("empty");
Graphics2D g = cvImg.createGraphics();
g.drawImage(img, 0, 0, null);
g.dispose();
timer.lap("created");
data = ((DataBufferByte) cvImg.getRaster().getDataBuffer()).getData();
mat_ref.put(0, 0, data);
Mat mat = new Mat();
timer.lap("filled");
Imgproc.cvtColor(mat_ref, mat, Imgproc.COLOR_RGBA2BGR, 3);
timer.end();
return mat;
} else {
return null;
}
}
/**
* to get old style OpenCV Mat for FindInput
*
* @return SWIG interfaced OpenCV Mat
* @deprecated
*/
@Deprecated
protected org.sikuli.natives.Mat getMatNative() {
return convertBufferedImageToMat(get());
}
protected static org.sikuli.natives.Mat convertBufferedImageToMat(BufferedImage img) {
if (img != null) {
long theMatTime = new Date().getTime();
byte[] data = convertBufferedImageToByteArray(img);
org.sikuli.natives.Mat theMat = Vision.createMat(img.getHeight(), img.getWidth(), data);
if (Settings.FindProfiling) {
Debug.logp("[FindProfiling] createCVMat [%d x %d]: %d msec",
img.getWidth(), img.getHeight(), new Date().getTime() - theMatTime);
}
return theMat;
} else {
return null;
}
}
protected static byte[] convertBufferedImageToByteArray(BufferedImage img) {
if (img != null) {
BufferedImage cvImg = createBufferedImage(img.getWidth(), img.getHeight());
Graphics2D g = cvImg.createGraphics();
g.drawImage(img, 0, 0, null);
g.dispose();
return ((DataBufferByte) cvImg.getRaster().getDataBuffer()).getData();
} else {
return null;
}
}
protected static BufferedImage createBufferedImage(int w, int h) {
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
int[] nBits = {8, 8, 8, 8};
ColorModel cm = new ComponentColorModel(cs, nBits, true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
SampleModel sm = cm.createCompatibleSampleModel(w, h);
DataBufferByte db = new DataBufferByte(w * h * 4);
WritableRaster r = WritableRaster.createWritableRaster(sm, db, new Point(0, 0));
BufferedImage bm = new BufferedImage(cm, r, false, null);
return bm;
}
// **************** for Tesseract4Java ********************
/**
* Converts BufferedImage
to ByteBuffer
.
*
* @param bi Input image
* @return pixel data
*/
public static ByteBuffer convertImageData(BufferedImage bi) {
DataBuffer buff = bi.getRaster().getDataBuffer();
// ClassCastException thrown if buff not instanceof DataBufferByte because raster data is not necessarily bytes.
// Convert the original buffered image to grayscale.
if (!(buff instanceof DataBufferByte)) {
bi = convertImageToGrayscale(bi);
buff = bi.getRaster().getDataBuffer();
}
byte[] pixelData = ((DataBufferByte) buff).getData();
// return ByteBuffer.wrap(pixelData);
ByteBuffer buf = ByteBuffer.allocateDirect(pixelData.length);
buf.order(ByteOrder.nativeOrder());
buf.put(pixelData);
buf.flip();
return buf;
}
/**
* A simple method to convert an image to gray scale.
*
* @param image input image
* @return a monochrome image
*/
public static BufferedImage convertImageToGrayscale(BufferedImage image) {
BufferedImage tmp = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
Graphics2D g2 = tmp.createGraphics();
g2.drawImage(image, 0, 0, null);
g2.dispose();
return tmp;
}
/**
* find an image in another image
* @param img image
* @return a Match or null
*/
public Match find(Image img) {
log(-1, "find: not implemented yet");
return null;
}
/**
* find all images in another image
* @param img image
* @return Match or null
*/
public Iterator findAll(Image img) {
log(-1, "findAll: not implemented yet");
return null;
}
/**
* OCR-read the text from the image
* @return the text or null
*/
public String text() {
//TODO: use Tess4J here already??
if (Settings.OcrTextRead) {
TextRecognizer tr = TextRecognizer.getInstance();
if (tr == null) {
Debug.error("text: text recognition is now switched off");
return null;
}
String textRead = tr.recognize(this.get());
log(lvl, "text: #(" + textRead + ")#");
return textRead;
}
Debug.error("text: text recognition is currently switched off");
return null;
}
/**
* convenience method: get text from given image file
* @param imgFile image filename
* @return the text or null
*/
public static String text(String imgFile) {
return create(imgFile).text();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy