2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
31 import java
.awt
.image
.BufferedImage
;
33 import jexer
.backend
.ECMA48Terminal
;
34 import jexer
.backend
.MultiScreen
;
35 import jexer
.backend
.SwingTerminal
;
36 import jexer
.bits
.Cell
;
37 import jexer
.event
.TKeypressEvent
;
38 import jexer
.event
.TMouseEvent
;
39 import static jexer
.TKeypress
.*;
42 * TImage renders a piece of a bitmap image on screen.
44 public class TImage
extends TWidget
{
46 // ------------------------------------------------------------------------
47 // Variables --------------------------------------------------------------
48 // ------------------------------------------------------------------------
51 * The action to perform when the user clicks on the image.
53 private TAction clickAction
;
56 * The image to display.
58 private BufferedImage image
;
61 * The original image from construction time.
63 private BufferedImage originalImage
;
66 * The current scaling factor for the image.
68 private double scaleFactor
= 1.0;
71 * The current clockwise rotation for the image.
73 private int clockwise
= 0;
76 * Left column of the image. 0 is the left-most column.
81 * Top row of the image. 0 is the top-most row.
86 * The cells containing the broken up image pieces.
88 private Cell cells
[][];
91 * The number of rows in cells[].
96 * The number of columns in cells[].
98 private int cellColumns
;
101 * Last text width value.
103 private int lastTextWidth
= -1;
106 * Last text height value.
108 private int lastTextHeight
= -1;
110 // ------------------------------------------------------------------------
111 // Constructors -----------------------------------------------------------
112 // ------------------------------------------------------------------------
115 * Public constructor.
117 * @param parent parent widget
118 * @param x column relative to parent
119 * @param y row relative to parent
120 * @param width number of text cells for width of the image
121 * @param height number of text cells for height of the image
122 * @param image the image to display
123 * @param left left column of the image. 0 is the left-most column.
124 * @param top top row of the image. 0 is the top-most row.
126 public TImage(final TWidget parent
, final int x
, final int y
,
127 final int width
, final int height
,
128 final BufferedImage image
, final int left
, final int top
) {
130 this(parent
, x
, y
, width
, height
, image
, left
, top
, null);
134 * Public constructor.
136 * @param parent parent widget
137 * @param x column relative to parent
138 * @param y row relative to parent
139 * @param width number of text cells for width of the image
140 * @param height number of text cells for height of the image
141 * @param image the image to display
142 * @param left left column of the image. 0 is the left-most column.
143 * @param top top row of the image. 0 is the top-most row.
144 * @param clickAction function to call when mouse is pressed
146 public TImage(final TWidget parent
, final int x
, final int y
,
147 final int width
, final int height
,
148 final BufferedImage image
, final int left
, final int top
,
149 final TAction clickAction
) {
151 // Set parent and window
152 super(parent
, x
, y
, width
, height
);
154 setCursorVisible(false);
155 this.originalImage
= image
;
158 this.clickAction
= clickAction
;
162 getApplication().addImage(this);
165 // ------------------------------------------------------------------------
166 // Event handlers ---------------------------------------------------------
167 // ------------------------------------------------------------------------
170 * Subclasses should override this method to cleanup resources. This is
171 * called by TWindow.onClose().
174 protected void close() {
175 getApplication().removeImage(this);
180 * Handle mouse press events.
182 * @param mouse mouse button press event
185 public void onMouseDown(final TMouseEvent mouse
) {
186 if (clickAction
!= null) {
195 * @param keypress keystroke event
198 public void onKeypress(final TKeypressEvent keypress
) {
199 if (!keypress
.getKey().isFnKey()) {
200 if (keypress
.getKey().getChar() == '+') {
201 // Make the image bigger.
207 if (keypress
.getKey().getChar() == '-') {
208 // Make the image smaller.
215 if (keypress
.equals(kbAltUp
)) {
216 // Make the image bigger.
222 if (keypress
.equals(kbAltDown
)) {
223 // Make the image smaller.
229 if (keypress
.equals(kbAltRight
)) {
237 if (keypress
.equals(kbAltLeft
)) {
238 // Rotate counter-clockwise.
248 // Pass to parent for the things we don't care about.
249 super.onKeypress(keypress
);
252 // ------------------------------------------------------------------------
253 // TWidget ----------------------------------------------------------------
254 // ------------------------------------------------------------------------
263 // We have already broken the image up, just draw the last set of
265 for (int x
= 0; (x
< getWidth()) && (x
+ left
< cellColumns
); x
++) {
266 if ((left
+ x
) * lastTextWidth
> image
.getWidth()) {
270 for (int y
= 0; (y
< getHeight()) && (y
+ top
< cellRows
); y
++) {
271 if ((top
+ y
) * lastTextHeight
> image
.getHeight()) {
274 assert (x
+ left
< cellColumns
);
275 assert (y
+ top
< cellRows
);
277 getWindow().putCharXY(x
, y
, cells
[x
+ left
][y
+ top
]);
283 // ------------------------------------------------------------------------
284 // TImage -----------------------------------------------------------------
285 // ------------------------------------------------------------------------
288 * Size cells[][] according to the screen font size.
290 * @param always if true, always resize the cells
292 private void sizeToImage(final boolean always
) {
296 if (getScreen() instanceof SwingTerminal
) {
297 SwingTerminal terminal
= (SwingTerminal
) getScreen();
299 textWidth
= terminal
.getTextWidth();
300 textHeight
= terminal
.getTextHeight();
301 } if (getScreen() instanceof MultiScreen
) {
302 MultiScreen terminal
= (MultiScreen
) getScreen();
304 textWidth
= terminal
.getTextWidth();
305 textHeight
= terminal
.getTextHeight();
306 } else if (getScreen() instanceof ECMA48Terminal
) {
307 ECMA48Terminal terminal
= (ECMA48Terminal
) getScreen();
309 textWidth
= terminal
.getTextWidth();
310 textHeight
= terminal
.getTextHeight();
314 image
= scaleImage(originalImage
, scaleFactor
);
315 image
= rotateImage(image
, clockwise
);
318 if ((always
== true) ||
320 && (textWidth
!= lastTextWidth
)
322 && (textHeight
!= lastTextHeight
))
324 cellColumns
= image
.getWidth() / textWidth
;
325 if (cellColumns
* textWidth
< image
.getWidth()) {
328 cellRows
= image
.getHeight() / textHeight
;
329 if (cellRows
* textHeight
< image
.getHeight()) {
333 // Break the image up into an array of cells.
334 cells
= new Cell
[cellColumns
][cellRows
];
336 for (int x
= 0; x
< cellColumns
; x
++) {
337 for (int y
= 0; y
< cellRows
; y
++) {
339 int width
= textWidth
;
340 if ((x
+ 1) * textWidth
> image
.getWidth()) {
341 width
= image
.getWidth() - (x
* textWidth
);
343 int height
= textHeight
;
344 if ((y
+ 1) * textHeight
> image
.getHeight()) {
345 height
= image
.getHeight() - (y
* textHeight
);
348 Cell cell
= new Cell();
349 cell
.setImage(image
.getSubimage(x
* textWidth
,
350 y
* textHeight
, width
, height
));
356 lastTextWidth
= textWidth
;
357 lastTextHeight
= textHeight
;
360 if ((left
+ getWidth()) > cellColumns
) {
361 left
= cellColumns
- getWidth();
366 if ((top
+ getHeight()) > cellRows
) {
367 top
= cellRows
- getHeight();
375 * Get the top corner to render.
377 * @return the top row
379 public int getTop() {
384 * Set the top corner to render.
386 * @param top the new top row
388 public void setTop(final int top
) {
390 if (this.top
> cellRows
- getHeight()) {
391 this.top
= cellRows
- getHeight();
399 * Get the left corner to render.
401 * @return the left column
403 public int getLeft() {
408 * Set the left corner to render.
410 * @param left the new left column
412 public void setLeft(final int left
) {
414 if (this.left
> cellColumns
- getWidth()) {
415 this.left
= cellColumns
- getWidth();
423 * Get the number of text cell rows for this image.
425 * @return the number of rows
427 public int getRows() {
432 * Get the number of text cell columns for this image.
434 * @return the number of columns
436 public int getColumns() {
441 * Scale an image by to be scaleFactor size.
443 * @param image the image to scale
444 * @param factor the scale to make the new image
446 private BufferedImage
scaleImage(final BufferedImage image
,
447 final double factor
) {
449 if (Math
.abs(factor
- 1.0) < 0.03) {
450 // If we are within 3% of 1.0, just return the original image.
454 int width
= (int) (image
.getWidth() * factor
);
455 int height
= (int) (image
.getHeight() * factor
);
457 BufferedImage newImage
= new BufferedImage(width
, height
,
458 BufferedImage
.TYPE_INT_ARGB
);
460 java
.awt
.Graphics gr
= newImage
.createGraphics();
461 gr
.drawImage(image
, 0, 0, width
, height
, null);
468 * Rotate an image either clockwise or counterclockwise.
470 * @param image the image to scale
471 * @param clockwise number of turns clockwise
473 private BufferedImage
rotateImage(final BufferedImage image
,
474 final int clockwise
) {
476 if (clockwise
% 4 == 0) {
480 BufferedImage newImage
= null;
482 if (clockwise
% 4 == 1) {
483 // 90 degrees clockwise
484 newImage
= new BufferedImage(image
.getHeight(), image
.getWidth(),
485 BufferedImage
.TYPE_INT_ARGB
);
486 for (int x
= 0; x
< image
.getWidth(); x
++) {
487 for (int y
= 0; y
< image
.getHeight(); y
++) {
488 newImage
.setRGB(y
, x
,
489 image
.getRGB(x
, image
.getHeight() - 1 - y
));
492 } else if (clockwise
% 4 == 2) {
493 // 180 degrees clockwise
494 newImage
= new BufferedImage(image
.getWidth(), image
.getHeight(),
495 BufferedImage
.TYPE_INT_ARGB
);
496 for (int x
= 0; x
< image
.getWidth(); x
++) {
497 for (int y
= 0; y
< image
.getHeight(); y
++) {
498 newImage
.setRGB(x
, y
,
499 image
.getRGB(image
.getWidth() - 1 - x
,
500 image
.getHeight() - 1 - y
));
503 } else if (clockwise
% 4 == 3) {
504 // 270 degrees clockwise
505 newImage
= new BufferedImage(image
.getHeight(), image
.getWidth(),
506 BufferedImage
.TYPE_INT_ARGB
);
507 for (int x
= 0; x
< image
.getWidth(); x
++) {
508 for (int y
= 0; y
< image
.getHeight(); y
++) {
509 newImage
.setRGB(y
, x
,
510 image
.getRGB(image
.getWidth() - 1 - x
, y
));