Skip to content

Commit b04b87a

Browse files
author
Nadeem Anwar
committed
"- Merged with latest commit in the main branch"
2 parents 7d1359e + 7e0f523 commit b04b87a

7 files changed

Lines changed: 786 additions & 41 deletions

File tree

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package com.termux.terminal;
2+
3+
import java.util.Arrays;
4+
import java.util.HashSet;
5+
import java.util.Set;
6+
7+
import android.graphics.Bitmap;
8+
import android.graphics.BitmapFactory;
9+
import android.graphics.Rect;
10+
11+
import android.os.SystemClock;
12+
13+
/**
14+
* A circular buffer of {@link TerminalRow}:s which keeps notes about what is visible on a logical screen and the scroll
15+
* history.
16+
* <p>
17+
* See {@link #externalToInternalRow(int)} for how to map from logical screen rows to array indices.
18+
*/
19+
public class TerminalBitmap {
20+
public Bitmap bitmap;
21+
public int cellWidth;
22+
public int cellHeight;
23+
public int scrollLines;
24+
public int[] cursorDelta;
25+
private static final String LOG_TAG = "TerminalBitmap";
26+
27+
28+
public TerminalBitmap(int num, WorkingTerminalBitmap sixel, int Y, int X, int cellW, int cellH, TerminalBuffer screen) {
29+
Bitmap bm = sixel.bitmap;
30+
bm = resizeBitmapConstraints(bm, sixel.width, sixel.height, cellW, cellH, screen.mColumns - X);
31+
addBitmap(num, bm, Y, X, cellW, cellH, screen);
32+
}
33+
34+
public TerminalBitmap(int num, byte[] image, int Y, int X, int cellW, int cellH, int width, int height, boolean aspect, TerminalBuffer screen) {
35+
Bitmap bm = null;
36+
int imageHeight;
37+
int imageWidth;
38+
int newWidth = width;
39+
int newHeight = height;
40+
if (height > 0 || width > 0) {
41+
BitmapFactory.Options options = new BitmapFactory.Options();
42+
options.inJustDecodeBounds = true;
43+
try {
44+
BitmapFactory.decodeByteArray(image, 0, image.length, options);
45+
} catch (Exception e) {
46+
Logger.logWarn(null, LOG_TAG, "Cannot decode image");
47+
}
48+
imageHeight = options.outHeight;
49+
imageWidth = options.outWidth;
50+
if (aspect) {
51+
double wFactor = 9999.0;
52+
double hFactor = 9999.0;
53+
if (width > 0) {
54+
wFactor = (double)width / imageWidth;
55+
}
56+
if (height > 0) {
57+
hFactor = (double)height / imageHeight;
58+
}
59+
double factor = Math.min(wFactor, hFactor);
60+
newWidth = (int)(factor * imageWidth);
61+
newHeight = (int)(factor * imageHeight);
62+
} else {
63+
if (height <= 0) {
64+
newHeight = imageHeight;
65+
}
66+
if (width <= 0) {
67+
newWidth = imageWidth;
68+
}
69+
}
70+
int scaleFactor = 1;
71+
while (imageHeight >= 2 * newHeight * scaleFactor && imageWidth >= 2 * newWidth * scaleFactor) {
72+
scaleFactor = scaleFactor * 2;
73+
}
74+
BitmapFactory.Options scaleOptions = new BitmapFactory.Options();
75+
scaleOptions.inSampleSize = scaleFactor;
76+
try {
77+
bm = BitmapFactory.decodeByteArray(image, 0, image.length, scaleOptions);
78+
} catch (Exception e) {
79+
Logger.logWarn(null, LOG_TAG, "Out of memory, cannot decode image");
80+
bitmap = null;
81+
return;
82+
}
83+
if (bm == null) {
84+
Logger.logWarn(null, LOG_TAG, "Could not decode image");
85+
bitmap = null;
86+
return;
87+
}
88+
int maxWidth = (screen.mColumns - X) * cellW;
89+
if (newWidth > maxWidth) {
90+
int cropWidth = bm.getWidth() * maxWidth / newWidth;
91+
try {
92+
bm = Bitmap.createBitmap(bm, 0, 0, cropWidth, bm.getHeight());
93+
newWidth = maxWidth;
94+
} catch(OutOfMemoryError e) {
95+
// This is just a memory optimization. If it fails,
96+
// continue (and probably fail later).
97+
}
98+
}
99+
try {
100+
bm = Bitmap.createScaledBitmap(bm, newWidth, newHeight, true);
101+
} catch(OutOfMemoryError e) {
102+
Logger.logWarn(null, LOG_TAG, "Out of memory, cannot rescale image");
103+
bm = null;
104+
}
105+
} else {
106+
try {
107+
bm = BitmapFactory.decodeByteArray(image, 0, image.length);
108+
} catch (OutOfMemoryError e) {
109+
Logger.logWarn(null, LOG_TAG, "Out of memory, cannot decode image");
110+
}
111+
}
112+
113+
if (bm == null) {
114+
Logger.logWarn(null, LOG_TAG, "Cannot decode image");
115+
bitmap = null;
116+
return;
117+
}
118+
119+
bm = resizeBitmapConstraints(bm, bm.getWidth(), bm.getHeight(), cellW, cellH, screen.mColumns - X);
120+
addBitmap(num, bm, Y, X, cellW, cellH, screen);
121+
cursorDelta = new int[] {scrollLines, (bitmap.getWidth() + cellW - 1) / cellW};
122+
}
123+
124+
private void addBitmap(int num, Bitmap bm, int Y, int X, int cellW, int cellH, TerminalBuffer screen) {
125+
if (bm == null) {
126+
bitmap = null;
127+
return;
128+
}
129+
int width = bm.getWidth();
130+
int height = bm.getHeight();
131+
cellWidth = cellW;
132+
cellHeight = cellH;
133+
int w = Math.min(screen.mColumns - X, (width + cellW - 1) / cellW);
134+
int h = (height + cellH - 1) / cellH;
135+
int s = 0;
136+
for (int i=0; i<h; i++) {
137+
if (Y+i-s == screen.mScreenRows) {
138+
screen.scrollDownOneLine(0, screen.mScreenRows, TextStyle.NORMAL);
139+
s++;
140+
}
141+
for (int j=0; j<w ; j++) {
142+
screen.setChar(X+j, Y+i-s, '+', TextStyle.encodeBitmap(num, j, i));
143+
}
144+
}
145+
if (w * cellW < width) {
146+
try {
147+
bm = Bitmap.createBitmap(bm, 0, 0, w * cellW, height);
148+
} catch(OutOfMemoryError e) {
149+
// Image cannot be cropped to only visible part due to out of memory.
150+
// This causes memory waste.
151+
}
152+
}
153+
bitmap = bm;
154+
scrollLines = h - s;
155+
}
156+
157+
static public Bitmap resizeBitmap(Bitmap bm, int w, int h) {
158+
int[] pixels = new int[bm.getAllocationByteCount()];
159+
bm.getPixels(pixels, 0, bm.getWidth(), 0, 0, bm.getWidth(), bm.getHeight());
160+
Bitmap newbm;
161+
try {
162+
newbm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
163+
} catch(OutOfMemoryError e) {
164+
// Only a minor display glitch in this case
165+
return bm;
166+
}
167+
int newWidth = Math.min(bm.getWidth(), w);
168+
int newHeight = Math.min(bm.getHeight(), h);
169+
newbm.setPixels(pixels, 0, bm.getWidth(), 0, 0, newWidth, newHeight);
170+
return newbm;
171+
}
172+
173+
static public Bitmap resizeBitmapConstraints(Bitmap bm, int w, int h, int cellW, int cellH, int Columns) {
174+
// Width and height must be multiples of the cell width and height
175+
// Bitmap should not extend beyonf screen width
176+
if (w > cellW * Columns || (w % cellW) != 0 || (h % cellH) != 0) {
177+
int newW = Math.min(cellW * Columns, ((w - 1) / cellW) * cellW + cellW);
178+
int newH = ((h - 1) / cellH) * cellH + cellH;
179+
try {
180+
bm = resizeBitmap(bm, newW, newH);
181+
} catch(OutOfMemoryError e) {
182+
// Only a minor display glitch in this case
183+
}
184+
}
185+
return bm;
186+
}
187+
}

terminal-emulator/src/main/java/com/termux/terminal/TerminalBuffer.java

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
package com.termux.terminal;
22

33
import java.util.Arrays;
4+
import java.util.HashSet;
5+
import java.util.Set;
6+
import java.util.HashMap;
7+
8+
import android.graphics.Bitmap;
9+
import android.graphics.BitmapFactory;
10+
import android.graphics.Rect;
11+
12+
import android.os.SystemClock;
413

514
/**
615
* A circular buffer of {@link TerminalRow}:s which keeps notes about what is visible on a logical screen and the scroll
@@ -20,6 +29,12 @@ public final class TerminalBuffer {
2029
/** The index in the circular buffer where the visible screen starts. */
2130
private int mScreenFirstRow = 0;
2231

32+
public HashMap<Integer,TerminalBitmap> bitmaps;
33+
public WorkingTerminalBitmap workingBitmap;
34+
private boolean hasBitmaps;
35+
private long bitmapLastGC;
36+
37+
2338
/**
2439
* Create a transcript screen.
2540
*
@@ -35,6 +50,9 @@ public TerminalBuffer(int columns, int totalRows, int screenRows) {
3550
mLines = new TerminalRow[totalRows];
3651

3752
blockSet(0, 0, columns, screenRows, ' ', TextStyle.NORMAL);
53+
hasBitmaps = false;
54+
bitmaps = new HashMap<Integer,TerminalBitmap>();
55+
bitmapLastGC = SystemClock.uptimeMillis();
3856
}
3957

4058
public String getTranscriptText() {
@@ -401,6 +419,28 @@ public void scrollDownOneLine(int topMargin, int bottomMargin, long style) {
401419
if (mLines[blankRow] == null) {
402420
mLines[blankRow] = new TerminalRow(mColumns, style);
403421
} else {
422+
// find if a bitmap is completely scrolled out
423+
Set<Integer> used = new HashSet<Integer>();
424+
if(mLines[blankRow].mHasBitmap) {
425+
for (int column = 0; column < mColumns; column++) {
426+
final long st = mLines[blankRow].getStyle(column);
427+
if (TextStyle.isBitmap(st)) {
428+
used.add((int)(st >> 16) & 0xffff);
429+
}
430+
}
431+
TerminalRow nextLine = mLines[(blankRow + 1) % mTotalRows];
432+
if(nextLine.mHasBitmap) {
433+
for (int column = 0; column < mColumns; column++) {
434+
final long st = nextLine.getStyle(column);
435+
if (TextStyle.isBitmap(st)) {
436+
used.remove((int)(st >> 16) & 0xffff);
437+
}
438+
}
439+
}
440+
for(Integer bm: used) {
441+
bitmaps.remove(bm);
442+
}
443+
}
404444
mLines[blankRow].clear(style);
405445
}
406446
}
@@ -439,9 +479,13 @@ public void blockSet(int sx, int sy, int w, int h, int val, long style) {
439479
throw new IllegalArgumentException(
440480
"Illegal arguments! blockSet(" + sx + ", " + sy + ", " + w + ", " + h + ", " + val + ", " + mColumns + ", " + mScreenRows + ")");
441481
}
442-
for (int y = 0; y < h; y++)
482+
for (int y = 0; y < h; y++) {
443483
for (int x = 0; x < w; x++)
444484
setChar(sx + x, sy + y, val, style);
485+
if (sx+w == mColumns && val == ' ') {
486+
clearLineWrap(sy + y);
487+
}
488+
}
445489
}
446490

447491
public TerminalRow allocateFullLineIfNecessary(int row) {
@@ -492,6 +536,92 @@ public void clearTranscript() {
492536
Arrays.fill(mLines, mScreenFirstRow - mActiveTranscriptRows, mScreenFirstRow, null);
493537
}
494538
mActiveTranscriptRows = 0;
539+
bitmaps.clear();
540+
hasBitmaps = false;
541+
}
542+
543+
public Bitmap getSixelBitmap(int codePoint, long style) {
544+
return bitmaps.get(TextStyle.bitmapNum(style)).bitmap;
545+
}
546+
547+
public Rect getSixelRect(int codePoint, long style ) {
548+
TerminalBitmap bm = bitmaps.get(TextStyle.bitmapNum(style));
549+
int x = TextStyle.bitmapX(style);
550+
int y = TextStyle.bitmapY(style);
551+
Rect r = new Rect(x * bm.cellWidth, y * bm.cellHeight, (x+1) * bm.cellWidth, (y+1) * bm.cellHeight);
552+
return r;
553+
}
554+
555+
public void sixelStart(int width, int height) {
556+
workingBitmap = new WorkingTerminalBitmap(width, height);
557+
}
558+
559+
public void sixelChar(int c, int rep) {
560+
workingBitmap.sixelChar(c, rep);
495561
}
496562

563+
public void sixelSetColor(int col) {
564+
workingBitmap.sixelSetColor(col);
565+
}
566+
567+
public void sixelSetColor(int col, int r, int g, int b) {
568+
workingBitmap.sixelSetColor(col, r, g, b);
569+
}
570+
571+
private int findFreeBitmap() {
572+
int i = 0;
573+
while (bitmaps.containsKey(i)) {
574+
i++;
575+
}
576+
return i;
577+
}
578+
579+
public int sixelEnd(int Y, int X, int cellW, int cellH) {
580+
int num = findFreeBitmap();
581+
bitmaps.put(num, new TerminalBitmap(num, workingBitmap, Y, X, cellW, cellH, this));
582+
workingBitmap = null;
583+
if (bitmaps.get(num).bitmap == null) {
584+
bitmaps.remove(num);
585+
return 0;
586+
}
587+
hasBitmaps = true;
588+
bitmapGC(30000);
589+
return bitmaps.get(num).scrollLines;
590+
}
591+
592+
public int[] addImage(byte[] image, int Y, int X, int cellW, int cellH, int width, int height, boolean aspect) {
593+
int num = findFreeBitmap();
594+
bitmaps.put(num, new TerminalBitmap(num, image, Y, X, cellW, cellH, width, height, aspect, this));
595+
if (bitmaps.get(num).bitmap == null) {
596+
bitmaps.remove(num);
597+
return new int[] {0,0};
598+
}
599+
hasBitmaps = true;
600+
bitmapGC(30000);
601+
return bitmaps.get(num).cursorDelta;
602+
}
603+
604+
public void bitmapGC(int timeDelta) {
605+
if (!hasBitmaps || bitmapLastGC + timeDelta > SystemClock.uptimeMillis()) {
606+
return;
607+
}
608+
Set<Integer> used = new HashSet<Integer>();
609+
for (int line = 0; line < mLines.length; line++) {
610+
if(mLines[line] != null && mLines[line].mHasBitmap) {
611+
for (int column = 0; column < mColumns; column++) {
612+
final long st = mLines[line].getStyle(column);
613+
if (TextStyle.isBitmap(st)) {
614+
used.add((int)(st >> 16) & 0xffff);
615+
}
616+
}
617+
}
618+
}
619+
Set<Integer> keys = new HashSet<Integer>(bitmaps.keySet());
620+
for (Integer bn: keys) {
621+
if (!used.contains(bn)) {
622+
bitmaps.remove(bn);
623+
}
624+
}
625+
bitmapLastGC = SystemClock.uptimeMillis();
626+
}
497627
}

0 commit comments

Comments
 (0)