mirror of
https://gitlab.com/AutumnMeowMeow/jexer
synced 2024-09-19 11:50:19 -06:00
Read iTerm2 images in terminal widget
This commit is contained in:
parent
cd90cc4f21
commit
86da95e0cf
3 changed files with 326 additions and 1 deletions
|
@ -379,6 +379,8 @@ public class TImage extends TWidget implements EditMenuUser {
|
|||
* @param always if true, always resize the cells
|
||||
*/
|
||||
private void sizeToImage(final boolean always) {
|
||||
scaleBackColor = jexer.backend.SwingTerminal.attrToBackgroundColor(getWindow().getBackground());
|
||||
|
||||
int textWidth = getScreen().getTextWidth();
|
||||
int textHeight = getScreen().getTextHeight();
|
||||
|
||||
|
@ -648,7 +650,7 @@ public class TImage extends TWidget implements EditMenuUser {
|
|||
}
|
||||
|
||||
/**
|
||||
* Scale an image by to be scaleFactor size.
|
||||
* Scale an image to be scaleFactor size, OR stretch it.
|
||||
*
|
||||
* @param image the image to scale
|
||||
* @param factor the scale to make the new image
|
||||
|
|
|
@ -35,9 +35,37 @@ import java.awt.image.BufferedImage;
|
|||
*
|
||||
* - Check if an image is fully transparent.
|
||||
*
|
||||
* - Scale an image and preserve aspect ratio.
|
||||
*/
|
||||
public class ImageUtils {
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Constants --------------------------------------------------------------
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Selections for fitting the image to the text cells.
|
||||
*/
|
||||
public enum Scale {
|
||||
/**
|
||||
* Stretch/shrink the image in both directions to fully fill the text
|
||||
* area width/height.
|
||||
*/
|
||||
STRETCH,
|
||||
|
||||
/**
|
||||
* Scale the image, preserving aspect ratio, to fill the text area
|
||||
* width/height (like letterbox). The background color for the
|
||||
* letterboxed area is specified in the backColor argument to
|
||||
* scaleImage().
|
||||
*/
|
||||
SCALE,
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// ImageUtils -------------------------------------------------------------
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Check if any pixels in an image have not-0% alpha value.
|
||||
*
|
||||
|
@ -92,4 +120,62 @@ public class ImageUtils {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale an image to be scaleFactor size and/or stretch it to fit a
|
||||
* target box.
|
||||
*
|
||||
* @param image the image to scale
|
||||
* @param width the width in pixels for the destination image
|
||||
* @param height the height in pixels for the destination image
|
||||
* @param scale the scaling type
|
||||
* @param backColor the background color to use for Scale.SCALE
|
||||
*/
|
||||
public static BufferedImage scaleImage(final BufferedImage image,
|
||||
final int width, final int height,
|
||||
final Scale scale, final java.awt.Color backColor) {
|
||||
|
||||
BufferedImage newImage = new BufferedImage(width, height,
|
||||
BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int destWidth = width;
|
||||
int destHeight = height;
|
||||
switch (scale) {
|
||||
case STRETCH:
|
||||
break;
|
||||
case SCALE:
|
||||
double a = (double) image.getWidth() / image.getHeight();
|
||||
double b = (double) width / height;
|
||||
double h = (double) height / image.getHeight();
|
||||
double w = (double) width / image.getWidth();
|
||||
assert (a > 0);
|
||||
assert (b > 0);
|
||||
|
||||
if (a > b) {
|
||||
// Horizontal letterbox
|
||||
destHeight = (int) (image.getWidth() / a * w);
|
||||
destWidth = (int) (image.getWidth() * w);
|
||||
y = (height - destHeight) / 2;
|
||||
assert (y >= 0);
|
||||
} else {
|
||||
// Vertical letterbox
|
||||
destHeight = (int) (image.getHeight() * h);
|
||||
destWidth = (int) (image.getHeight() * a * h);
|
||||
x = (width - destWidth) / 2;
|
||||
assert (x >= 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
java.awt.Graphics gr = newImage.createGraphics();
|
||||
if (scale == Scale.SCALE) {
|
||||
gr.setColor(backColor);
|
||||
gr.fillRect(0, 0, newImage.getWidth(), newImage.getHeight());
|
||||
}
|
||||
gr.drawImage(image, x, y, destWidth, destHeight, null);
|
||||
gr.dispose();
|
||||
return newImage;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5407,6 +5407,10 @@ public class ECMA48 implements Runnable {
|
|||
parseJexerImageFile(2, p[2], p[3]);
|
||||
}
|
||||
}
|
||||
|
||||
if (p[0].equals("1337")) {
|
||||
parseIterm2Image(p);
|
||||
}
|
||||
}
|
||||
|
||||
// Go to SCAN_GROUND state
|
||||
|
@ -8017,6 +8021,239 @@ public class ECMA48 implements Runnable {
|
|||
imageToCells(image, scroll, maybeTransparent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a iTerm2 image string into a bitmap image, and overlay that
|
||||
* image onto the text cells. See reference at:
|
||||
* https://iterm2.com/documentation-images.html
|
||||
*
|
||||
* @param args the arguments of the OSC 1337 sequence. args[0] will be
|
||||
* "1337".
|
||||
*/
|
||||
private void parseIterm2Image(final String [] args) {
|
||||
// If the file data is opaque, pass that to imageToCells().
|
||||
boolean maybeTransparent = true;
|
||||
|
||||
// See: https://github.com/wez/wezterm/issues/1424
|
||||
boolean doNotMoveCursor = false;
|
||||
|
||||
// We MUST see "inline=1". This terminal does NOT EVER write to the
|
||||
// filesystem. Ever.
|
||||
boolean sawInline = false;
|
||||
|
||||
boolean preserveAspectRatio = false;
|
||||
|
||||
// Image dimension options.
|
||||
String iTerm2Width = "auto";
|
||||
String iTerm2Height = "auto";
|
||||
|
||||
// File size. This is optional to most terminals. If it is
|
||||
// specified, then we will limit to 4MB.
|
||||
boolean gotSize = false;
|
||||
int size = -1;
|
||||
|
||||
if ((args.length < 2) || !args[0].equals("1337")
|
||||
|| !args[1].startsWith("File=")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Separate the arguments into key/values, and the base64-encoded
|
||||
// data payload.
|
||||
|
||||
// Remove the "File=" from the first argument.
|
||||
args[1] = args[1].substring(5);
|
||||
// System.err.println("args[1]: '" + args[1] + "'");
|
||||
|
||||
// Separate the last argument from the ":{base64}" part.
|
||||
String lastArg = args[args.length - 1];
|
||||
if (!lastArg.contains(":")) {
|
||||
return;
|
||||
}
|
||||
String data = lastArg.substring(lastArg.indexOf(':') + 1);
|
||||
if (data.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastArg = lastArg.substring(0, lastArg.length() - data.length() - 1);
|
||||
// System.err.println("lastArg: '" + lastArg + "'");
|
||||
HashMap<String, String> pairs = new HashMap<String, String>();
|
||||
for (int i = 1; i < args.length - 1; i++) {
|
||||
String [] pair = args[i].split("=");
|
||||
if (pair.length != 2) {
|
||||
return;
|
||||
}
|
||||
pairs.put(pair[0], pair[1]);
|
||||
}
|
||||
String [] pair = lastArg.split("=");
|
||||
if (pair.length != 2) {
|
||||
return;
|
||||
}
|
||||
pairs.put(pair[0], pair[1]);
|
||||
|
||||
// Now check the arguments
|
||||
for (String name: pairs.keySet()) {
|
||||
String value = pairs.get(name);
|
||||
|
||||
// System.err.println("name='" + name + "' value='" + value + "'");
|
||||
|
||||
if (name.equals("size")) {
|
||||
try {
|
||||
size = Integer.parseInt(value);
|
||||
gotSize = true;
|
||||
} catch (NumberFormatException e) {
|
||||
// SQUASH
|
||||
}
|
||||
}
|
||||
if (name.equals("inline") && value.equals("1")) {
|
||||
sawInline = true;
|
||||
}
|
||||
if (name.equals("width")) {
|
||||
iTerm2Width = value;
|
||||
}
|
||||
if (name.equals("height")) {
|
||||
iTerm2Height = value;
|
||||
}
|
||||
if (name.equals("preserveAspectRatio") && value.equals("1")) {
|
||||
preserveAspectRatio = true;
|
||||
}
|
||||
if (name.equals("doNotMoveCursor") && value.equals("1")) {
|
||||
doNotMoveCursor = true;
|
||||
}
|
||||
}
|
||||
if (!sawInline) {
|
||||
return;
|
||||
}
|
||||
if (gotSize) {
|
||||
if ((size < 1) || (size > 16777216)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We have the options and image data, and it will be displayed. Now
|
||||
// try to decode it into a bitmap. We go blindly into the night as
|
||||
// far as image format is concerned.
|
||||
BufferedImage image = null;
|
||||
byte [] bytes = StringUtils.fromBase64(data.getBytes());
|
||||
if (bytes == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
image = ImageIO.read(new ByteArrayInputStream(bytes));
|
||||
} catch (IOException e) {
|
||||
// SQUASH
|
||||
return;
|
||||
}
|
||||
assert (image != null);
|
||||
int fileImageWidth = image.getWidth();
|
||||
int fileImageHeight = image.getHeight();
|
||||
if ((fileImageWidth < 1)
|
||||
|| (fileImageWidth > 10000)
|
||||
|| (fileImageHeight < 1)
|
||||
|| (fileImageHeight > 10000)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (maybeTransparent) {
|
||||
if (image.getTransparency() == java.awt.Transparency.OPAQUE) {
|
||||
maybeTransparent = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Scale the image according to the width/height arguments.
|
||||
int displayWidth = fileImageWidth;
|
||||
int displayHeight = fileImageHeight;
|
||||
try {
|
||||
if (iTerm2Width.equals("auto")) {
|
||||
// NOP
|
||||
} else if (iTerm2Width.endsWith("%")) {
|
||||
// Percent of screen
|
||||
iTerm2Width = iTerm2Width.substring(0, iTerm2Width.length() - 1);
|
||||
int n = Integer.parseInt(iTerm2Width);
|
||||
if ((n < 0) || (n > 100)) {
|
||||
return;
|
||||
}
|
||||
displayWidth = (n * textWidth * width) / 100;
|
||||
} else if (iTerm2Width.endsWith("px")) {
|
||||
// Pixels
|
||||
iTerm2Width = iTerm2Width.substring(0, iTerm2Width.length() - 2);
|
||||
int n = Integer.parseInt(iTerm2Width);
|
||||
if (n < 0) {
|
||||
return;
|
||||
}
|
||||
displayWidth = n;
|
||||
} else {
|
||||
// Number of text cells
|
||||
int n = Integer.parseInt(iTerm2Width);
|
||||
if (n < 0) {
|
||||
return;
|
||||
}
|
||||
displayWidth = n * textWidth;
|
||||
}
|
||||
// Truncate images to fit the screen.
|
||||
displayWidth = Math.min(width * textWidth, displayWidth);
|
||||
|
||||
if (iTerm2Height.equals("auto")) {
|
||||
// NOP
|
||||
} else if (iTerm2Height.endsWith("%")) {
|
||||
// Percent of screen
|
||||
iTerm2Height = iTerm2Height.substring(0, iTerm2Height.length() - 1);
|
||||
int n = Integer.parseInt(iTerm2Height);
|
||||
if ((n < 0) || (n > 100)) {
|
||||
return;
|
||||
}
|
||||
displayHeight = (n * textHeight * height) / 100;
|
||||
} else if (iTerm2Height.endsWith("px")) {
|
||||
// Pixels
|
||||
iTerm2Height = iTerm2Height.substring(0, iTerm2Height.length() - 2);
|
||||
int n = Integer.parseInt(iTerm2Height);
|
||||
if (n < 0) {
|
||||
return;
|
||||
}
|
||||
displayHeight = n;
|
||||
} else {
|
||||
// Number of text cells
|
||||
int n = Integer.parseInt(iTerm2Height);
|
||||
if (n < 0) {
|
||||
return;
|
||||
}
|
||||
displayHeight = n * textHeight;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
// Invalid number, done.
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
System.err.println("File dims " + fileImageWidth + "x" +
|
||||
fileImageHeight +
|
||||
"Disp dims " + displayWidth + "x" + displayHeight);
|
||||
*/
|
||||
|
||||
if (doNotMoveCursor) {
|
||||
// Truncate image height to fit the screen.
|
||||
displayHeight = Math.min(height * textHeight, displayHeight);
|
||||
}
|
||||
|
||||
if (preserveAspectRatio
|
||||
&& ((displayWidth != fileImageWidth)
|
||||
|| (displayHeight != fileImageHeight))
|
||||
) {
|
||||
// Scale the image to fit the requested dimensions.
|
||||
image = ImageUtils.scaleImage(image, displayWidth, displayHeight,
|
||||
ImageUtils.Scale.SCALE,
|
||||
SwingTerminal.attrToBackgroundColor(currentState.attr));
|
||||
} else if ((displayWidth != fileImageWidth)
|
||||
|| (displayHeight != fileImageHeight)
|
||||
) {
|
||||
// Scale the image to fit the requested dimensions.
|
||||
image = ImageUtils.scaleImage(image, displayWidth, displayHeight,
|
||||
ImageUtils.Scale.STRETCH,
|
||||
SwingTerminal.attrToBackgroundColor(currentState.attr));
|
||||
}
|
||||
|
||||
imageToCells(image, !doNotMoveCursor, maybeTransparent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Break up an image into the cells at the current cursor.
|
||||
*
|
||||
|
|
Loading…
Reference in a new issue