mirror of
https://gitlab.com/AutumnMeowMeow/jexer
synced 2024-09-19 11:50:19 -06:00
#91 Use DECSDM to (some) sixel images on the bottom text row
This commit is contained in:
parent
f24d802400
commit
8953b42c71
5 changed files with 151 additions and 30 deletions
|
@ -498,6 +498,15 @@ public class ECMA48Terminal extends LogicalScreen
|
|||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Get th number of entries in the cache.
|
||||
*
|
||||
* @return the number of entries
|
||||
*/
|
||||
public int size() {
|
||||
return cache.size();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
@ -1262,7 +1271,7 @@ public class ECMA48Terminal extends LogicalScreen
|
|||
if (sessionInfo.getWindowWidth() > 0) {
|
||||
return (widthPixels / sessionInfo.getWindowWidth());
|
||||
}
|
||||
return 16;
|
||||
return 10;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3346,20 +3355,28 @@ public class ECMA48Terminal extends LogicalScreen
|
|||
}
|
||||
|
||||
if (y == height - 1) {
|
||||
// We are on the bottom row. If scrolling mode is enabled
|
||||
// (default), then VT320/xterm will scroll the entire screen if
|
||||
// we draw any pixels here. Do not draw the image, bail out
|
||||
// instead.
|
||||
sb.append(normal());
|
||||
sb.append(gotoXY(x, y));
|
||||
for (int j = 0; j < cells.size(); j++) {
|
||||
sb.append(' ');
|
||||
if (sixelEncoder instanceof HQSixelEncoder) {
|
||||
// HQ can emit images with transparency. We can use that
|
||||
// along with DECSDM to get up to 1000 pixel width images on
|
||||
// the bottom row.
|
||||
emitSixelOnBottomRow(x, y, cells, sb);
|
||||
return sb.toString();
|
||||
} else {
|
||||
// We are on the bottom row. If scrolling mode is enabled
|
||||
// (default), then VT320/xterm will scroll the entire screen if
|
||||
// we draw any pixels here. Do not draw the image, bail out
|
||||
// instead.
|
||||
sb.append(normal());
|
||||
sb.append(gotoXY(x, y));
|
||||
for (int j = 0; j < cells.size(); j++) {
|
||||
sb.append(' ');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
if (sixelCache == null) {
|
||||
sixelCache = new ImageCache(height * width / 2);
|
||||
sixelCache = new ImageCache(height * width * 10);
|
||||
}
|
||||
|
||||
// Save and get rows to/from the cache that do NOT have inverted
|
||||
|
@ -3388,11 +3405,22 @@ public class ECMA48Terminal extends LogicalScreen
|
|||
}
|
||||
|
||||
// If the final image would be larger than 1000 pixels wide, break it
|
||||
// up into smaller images.
|
||||
if (cells.size() * getTextWidth() > 1000) {
|
||||
// up into smaller images. Or if we are using the HQ encoder and
|
||||
// will have more than some multiple of the palette size in total
|
||||
// pixels.
|
||||
int maxChunkLength = 1000;
|
||||
if (sixelEncoder instanceof HQSixelEncoder) {
|
||||
maxChunkLength = Math.min(maxChunkLength,
|
||||
sixelEncoder.getPaletteSize() * 10 / getTextHeight());
|
||||
/*
|
||||
System.err.printf("maxChunkLength: %d cache used size %d\n",
|
||||
maxChunkLength, sixelCache.size());
|
||||
*/
|
||||
}
|
||||
if (cells.size() * getTextWidth() > maxChunkLength) {
|
||||
StringBuilder chunkSb = new StringBuilder();
|
||||
int chunkStart = 0;
|
||||
int chunkSize = 1000 / getTextWidth();
|
||||
int chunkSize = maxChunkLength / getTextWidth();
|
||||
int remaining = cells.size();
|
||||
int chunkX = x;
|
||||
ArrayList<Cell> chunk;
|
||||
|
@ -3418,6 +3446,51 @@ public class ECMA48Terminal extends LogicalScreen
|
|||
return (startSixel(x, y) + sixel + endSixel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sixel string representing a row of several cells containing
|
||||
* bitmap data on the bottom. This technique may not work on all
|
||||
* terminals, and is limited to 1000 pixels from the left edge.
|
||||
*
|
||||
* @param x column coordinate. 0 is the left-most column.
|
||||
* @param y row coordinate. 0 is the top-most row.
|
||||
* @param cells the cells containing the bitmap data
|
||||
* @param sb the StringBuilder to write to
|
||||
*/
|
||||
private void emitSixelOnBottomRow(final int x, final int y,
|
||||
final ArrayList<Cell> cells, final StringBuilder sb) {
|
||||
|
||||
int cellWidth = getTextWidth();
|
||||
int cellHeight = getTextHeight();
|
||||
int pixelX = x * cellWidth;
|
||||
int pixelY = y * cellHeight;
|
||||
int maxPixelX = pixelX + (cells.size() * cellWidth);
|
||||
int maxPixelY = pixelY + cellHeight;
|
||||
if ((maxPixelX > 1000) || (maxPixelY > 1000)) {
|
||||
// There is no point, xterm will not display this image.
|
||||
sb.append(normal());
|
||||
sb.append(gotoXY(x, y));
|
||||
for (int i = 0; i < cells.size(); i++) {
|
||||
sb.append(' ');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// The final image will be 1000 x 1000 or less.
|
||||
BufferedImage cellsImage = cellsToImage(cells);
|
||||
BufferedImage fullImage = new BufferedImage(maxPixelX,
|
||||
maxPixelY, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics gr = fullImage.getGraphics();
|
||||
gr.drawImage(cellsImage, pixelX, pixelY, null);
|
||||
gr.dispose();
|
||||
|
||||
// HQSixelEncoder.toSixel() can accept allowTransparent.
|
||||
String sixel = ((HQSixelEncoder) sixelEncoder).toSixel(fullImage, true);
|
||||
sb.append("\033[?80h\033P0;1;0q");
|
||||
sb.append(sixel);
|
||||
// System.err.println("SIXEL: " + sixel);
|
||||
sb.append("\033\\\033[?80l");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sixel support flag.
|
||||
*
|
||||
|
@ -3606,7 +3679,7 @@ public class ECMA48Terminal extends LogicalScreen
|
|||
}
|
||||
|
||||
if (iterm2Cache == null) {
|
||||
iterm2Cache = new ImageCache(height * width / 2);
|
||||
iterm2Cache = new ImageCache(height * width * 10);
|
||||
}
|
||||
|
||||
// Save and get rows to/from the cache that do NOT have inverted
|
||||
|
@ -3788,7 +3861,7 @@ public class ECMA48Terminal extends LogicalScreen
|
|||
}
|
||||
|
||||
if (jexerCache == null) {
|
||||
jexerCache = new ImageCache(height * width / 2);
|
||||
jexerCache = new ImageCache(height * width * 10);
|
||||
}
|
||||
|
||||
// Save and get rows to/from the cache that do NOT have inverted
|
||||
|
|
|
@ -858,7 +858,7 @@ public class MultiScreen implements Screen {
|
|||
* @return the width in pixels of a character cell
|
||||
*/
|
||||
public int getTextWidth() {
|
||||
int textWidth = 16;
|
||||
int textWidth = 10;
|
||||
synchronized (screens) {
|
||||
for (Screen screen: screens) {
|
||||
int newTextWidth = screen.getTextWidth();
|
||||
|
|
|
@ -632,17 +632,15 @@ public class Cell extends CellAttributes {
|
|||
return false;
|
||||
}
|
||||
// Either both objects have their image inverted, or neither do.
|
||||
if (imageHashCode == 0) {
|
||||
// Lazy-load hash code.
|
||||
imageHashCode = makeImageHashCode();
|
||||
}
|
||||
if (that.imageHashCode == 0) {
|
||||
// Lazy-load hash code.
|
||||
that.imageHashCode = that.makeImageHashCode();
|
||||
}
|
||||
if ((imageHashCode == that.imageHashCode)
|
||||
&& (background.equals(that.background))
|
||||
if ((imageHashCode != 0) && (that.imageHashCode != 0)
|
||||
&& (imageHashCode != that.imageHashCode)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (compareCellImages(this, that) == false) {
|
||||
return false;
|
||||
}
|
||||
if (background.equals(that.background)) {
|
||||
// Fall through to the attributes check below.
|
||||
// ...
|
||||
} else {
|
||||
|
@ -672,6 +670,53 @@ public class Cell extends CellAttributes {
|
|||
image.getWidth()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two Cell's images for equality. If the images are equal, then
|
||||
* the imageHashCode on both is set.
|
||||
*
|
||||
* @param first the first Cell
|
||||
* @param second the second Cell
|
||||
*/
|
||||
private boolean compareCellImages(final Cell first,
|
||||
final Cell second) {
|
||||
|
||||
assert (first.image != null);
|
||||
assert (second.image != null);
|
||||
|
||||
int width = first.image.getWidth();
|
||||
int height = first.image.getHeight();
|
||||
if (width != second.image.getWidth()) {
|
||||
return false;
|
||||
}
|
||||
if (height != second.image.getHeight()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int [] firstRgbArray = first.image.getRGB(0, 0, width, height,
|
||||
null, 0, width);
|
||||
int [] secondRgbArray = second.image.getRGB(0, 0, width, height,
|
||||
null, 0, width);
|
||||
|
||||
// This should be impossible, but check anyway.
|
||||
if (firstRgbArray.length != secondRgbArray.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int hashCode = 1;
|
||||
for (int i = 0; i < firstRgbArray.length; i++) {
|
||||
if (firstRgbArray[i] != secondRgbArray[i]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Integer.hashCode() was introduced in Java 1.8. It breaks the
|
||||
// original Jexer 1.0 dev goal for Java 1.6 compatibility.
|
||||
hashCode = 31 * hashCode + Integer.hashCode(firstRgbArray[i]);
|
||||
}
|
||||
first.imageHashCode = hashCode;
|
||||
second.imageHashCode = hashCode;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashcode uses all fields in equals().
|
||||
*
|
||||
|
|
|
@ -146,6 +146,9 @@ public class DemoApplication extends TApplication {
|
|||
(backendType == BackendType.SWING ? 28 : -1), 20);
|
||||
addAllWidgets();
|
||||
getBackend().setTitle(i18n.getString("applicationTitle"));
|
||||
|
||||
// Use cute theme by default.
|
||||
onMenu(new TMenuEvent(getBackend(), 10001));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
@ -204,7 +207,7 @@ public class DemoApplication extends TApplication {
|
|||
}
|
||||
|
||||
if (menu.getId() == 10001) {
|
||||
// Look sexy: switch the color theme, window borders, and button
|
||||
// Look cute: switch the color theme, window borders, and button
|
||||
// styles.
|
||||
System.setProperty("jexer.TWindow.borderStyleForeground", "round");
|
||||
System.setProperty("jexer.TWindow.borderStyleModal", "round");
|
||||
|
@ -320,7 +323,7 @@ public class DemoApplication extends TApplication {
|
|||
TMenu demoMenu = addMenu(i18n.getString("demo"));
|
||||
demoMenu.addItem(10000, i18n.getString("mainWindow"));
|
||||
demoMenu.addSeparator();
|
||||
demoMenu.addItem(10001, i18n.getString("lookSexy"));
|
||||
demoMenu.addItem(10001, i18n.getString("lookCute"));
|
||||
demoMenu.addItem(10002, i18n.getString("lookBland"));
|
||||
demoMenu.addSeparator();
|
||||
TMenuItem item = demoMenu.addItem(2000, i18n.getString("checkable"));
|
||||
|
|
|
@ -2,7 +2,7 @@ applicationTitle=Demo Application
|
|||
|
||||
demo=&Demo
|
||||
mainWindow=N&ew Main Window...
|
||||
lookSexy=Switch to sexy look
|
||||
lookCute=Switch to cute look
|
||||
lookBland=Switch to bland look
|
||||
checkable=&Checkable
|
||||
disabled=Disabled
|
||||
|
|
Loading…
Reference in a new issue