|
| 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 | +} |
0 commit comments