X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2FTImage.java;h=b7bfbd00a2ff165becc701575023b42e50b6deed;hb=HEAD;hp=bc827b709a4dd5e75bd1021f1c30c6ab11f19d49;hpb=382bc294dd88b71639fdd6c73216d2519635a080;p=fanfix.git diff --git a/src/jexer/TImage.java b/src/jexer/TImage.java index bc827b7..b7bfbd0 100644 --- a/src/jexer/TImage.java +++ b/src/jexer/TImage.java @@ -30,23 +30,60 @@ package jexer; import java.awt.image.BufferedImage; -import jexer.backend.ECMA48Terminal; -import jexer.backend.MultiScreen; -import jexer.backend.SwingTerminal; import jexer.bits.Cell; +import jexer.event.TCommandEvent; import jexer.event.TKeypressEvent; import jexer.event.TMouseEvent; +import jexer.event.TResizeEvent; +import static jexer.TCommand.*; import static jexer.TKeypress.*; /** * TImage renders a piece of a bitmap image on screen. */ -public class TImage extends TWidget { +public class TImage extends TWidget implements EditMenuUser { + + // ------------------------------------------------------------------------ + // Constants -------------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Selections for fitting the image to the text cells. + */ + public enum Scale { + /** + * No scaling. + */ + NONE, + + /** + * 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 scaleBackColor. + */ + SCALE, + } // ------------------------------------------------------------------------ // Variables -------------------------------------------------------------- // ------------------------------------------------------------------------ + /** + * Scaling strategy to use. + */ + private Scale scale = Scale.NONE; + + /** + * Scaling strategy to use. + */ + private java.awt.Color scaleBackColor = java.awt.Color.BLACK; + /** * The action to perform when the user clicks on the image. */ @@ -72,6 +109,12 @@ public class TImage extends TWidget { */ private int clockwise = 0; + /** + * If true, this widget was resized and a new scaled image must be + * produced. + */ + private boolean resized = false; + /** * Left column of the image. 0 is the left-most column. */ @@ -158,24 +201,12 @@ public class TImage extends TWidget { this.clickAction = clickAction; sizeToImage(true); - - getApplication().addImage(this); } // ------------------------------------------------------------------------ // Event handlers --------------------------------------------------------- // ------------------------------------------------------------------------ - /** - * Subclasses should override this method to cleanup resources. This is - * called by TWindow.onClose(). - */ - @Override - protected void close() { - getApplication().removeImage(this); - super.close(); - } - /** * Handle mouse press events. * @@ -184,7 +215,7 @@ public class TImage extends TWidget { @Override public void onMouseDown(final TMouseEvent mouse) { if (clickAction != null) { - clickAction.DO(); + clickAction.DO(this); return; } } @@ -245,10 +276,68 @@ public class TImage extends TWidget { return; } + if (keypress.equals(kbShiftLeft)) { + switch (scale) { + case NONE: + setScaleType(Scale.SCALE); + return; + case STRETCH: + setScaleType(Scale.NONE); + return; + case SCALE: + setScaleType(Scale.STRETCH); + return; + } + } + if (keypress.equals(kbShiftRight)) { + switch (scale) { + case NONE: + setScaleType(Scale.STRETCH); + return; + case STRETCH: + setScaleType(Scale.SCALE); + return; + case SCALE: + setScaleType(Scale.NONE); + return; + } + } + // Pass to parent for the things we don't care about. super.onKeypress(keypress); } + /** + * Handle resize events. + * + * @param event resize event + */ + @Override + public void onResize(final TResizeEvent event) { + // Get my width/height set correctly. + super.onResize(event); + + if (scale == Scale.NONE) { + return; + } + image = null; + resized = true; + } + + /** + * Handle posted command events. + * + * @param command command event + */ + @Override + public void onCommand(final TCommandEvent command) { + if (command.equals(cmCopy)) { + // Copy image to clipboard. + getClipboard().copyImage(image); + return; + } + } + // ------------------------------------------------------------------------ // TWidget ---------------------------------------------------------------- // ------------------------------------------------------------------------ @@ -290,37 +379,24 @@ public class TImage extends TWidget { * @param always if true, always resize the cells */ private void sizeToImage(final boolean always) { - int textWidth = 16; - int textHeight = 20; - - if (getScreen() instanceof SwingTerminal) { - SwingTerminal terminal = (SwingTerminal) getScreen(); - - textWidth = terminal.getTextWidth(); - textHeight = terminal.getTextHeight(); - } if (getScreen() instanceof MultiScreen) { - MultiScreen terminal = (MultiScreen) getScreen(); - - textWidth = terminal.getTextWidth(); - textHeight = terminal.getTextHeight(); - } else if (getScreen() instanceof ECMA48Terminal) { - ECMA48Terminal terminal = (ECMA48Terminal) getScreen(); - - textWidth = terminal.getTextWidth(); - textHeight = terminal.getTextHeight(); - } + int textWidth = getScreen().getTextWidth(); + int textHeight = getScreen().getTextHeight(); if (image == null) { - image = scaleImage(originalImage, scaleFactor); - image = rotateImage(image, clockwise); + image = rotateImage(originalImage, clockwise); + image = scaleImage(image, scaleFactor, getWidth(), getHeight(), + textWidth, textHeight); } if ((always == true) || + (resized == true) || ((textWidth > 0) && (textWidth != lastTextWidth) && (textHeight > 0) && (textHeight != lastTextHeight)) ) { + resized = false; + cellColumns = image.getWidth() / textWidth; if (cellColumns * textWidth < image.getWidth()) { cellColumns++; @@ -346,8 +422,21 @@ public class TImage extends TWidget { } Cell cell = new Cell(); - cell.setImage(image.getSubimage(x * textWidth, - y * textHeight, width, height)); + if ((width != textWidth) || (height != textHeight)) { + BufferedImage newImage; + newImage = new BufferedImage(textWidth, textHeight, + BufferedImage.TYPE_INT_ARGB); + + java.awt.Graphics gr = newImage.getGraphics(); + gr.drawImage(image.getSubimage(x * textWidth, + y * textHeight, width, height), + 0, 0, null, null); + gr.dispose(); + cell.setImage(newImage); + } else { + cell.setImage(image.getSubimage(x * textWidth, + y * textHeight, width, height)); + } cells[x][y] = cell; } @@ -437,30 +526,214 @@ public class TImage extends TWidget { return cellColumns; } + /** + * Get the raw (unprocessed) image. + * + * @return the image + */ + public BufferedImage getImage() { + return originalImage; + } + + /** + * Set the raw image, and reprocess to make the visible image. + * + * @param image the new image + */ + public void setImage(final BufferedImage image) { + this.originalImage = image; + this.image = null; + sizeToImage(true); + } + + /** + * Get the visible (processed) image. + * + * @return the image that is currently on screen + */ + public BufferedImage getVisibleImage() { + return image; + } + + /** + * Get the scaling strategy. + * + * @return Scale.NONE, Scale.STRETCH, etc. + */ + public Scale getScaleType() { + return scale; + } + + /** + * Set the scaling strategy. + * + * @param scale Scale.NONE, Scale.STRETCH, etc. + */ + public void setScaleType(final Scale scale) { + this.scale = scale; + this.image = null; + sizeToImage(true); + } + + /** + * Get the scale factor. + * + * @return the scale factor + */ + public double getScaleFactor() { + return scaleFactor; + } + + /** + * Set the scale factor. 1.0 means no scaling. + * + * @param scaleFactor the new scale factor + */ + public void setScaleFactor(final double scaleFactor) { + this.scaleFactor = scaleFactor; + image = null; + sizeToImage(true); + } + + /** + * Get the rotation, as degrees. + * + * @return the rotation in degrees + */ + public int getRotation() { + switch (clockwise) { + case 0: + return 0; + case 1: + return 90; + case 2: + return 180; + case 3: + return 270; + default: + // Don't know how this happened, but fix it. + clockwise = 0; + image = null; + sizeToImage(true); + return 0; + } + } + + /** + * Set the rotation, as degrees clockwise. + * + * @param rotation 0, 90, 180, or 270 + */ + public void setRotation(final int rotation) { + switch (rotation) { + case 0: + clockwise = 0; + break; + case 90: + clockwise = 1; + break; + case 180: + clockwise = 2; + break; + case 270: + clockwise = 3; + break; + default: + // Don't know how this happened, but fix it. + clockwise = 0; + break; + } + + image = null; + sizeToImage(true); + } + /** * Scale an image by to be scaleFactor size. * * @param image the image to scale * @param factor the scale to make the new image + * @param width the number of text cell columns for the destination image + * @param height the number of text cell rows for the destination image + * @param textWidth the width in pixels for one text cell + * @param textHeight the height in pixels for one text cell */ private BufferedImage scaleImage(final BufferedImage image, - final double factor) { + final double factor, final int width, final int height, + final int textWidth, final int textHeight) { - if (Math.abs(factor - 1.0) < 0.03) { + if ((scale == Scale.NONE) && (Math.abs(factor - 1.0) < 0.03)) { // If we are within 3% of 1.0, just return the original image. return image; } - int width = (int) (image.getWidth() * factor); - int height = (int) (image.getHeight() * factor); + int destWidth = 0; + int destHeight = 0; + int x = 0; + int y = 0; - BufferedImage newImage = new BufferedImage(width, height, - BufferedImage.TYPE_INT_ARGB); + BufferedImage newImage = null; + + switch (scale) { + case NONE: + destWidth = (int) (image.getWidth() * factor); + destHeight = (int) (image.getHeight() * factor); + newImage = new BufferedImage(destWidth, destHeight, + BufferedImage.TYPE_INT_ARGB); + break; + case STRETCH: + destWidth = width * textWidth; + destHeight = height * textHeight; + newImage = new BufferedImage(destWidth, destHeight, + BufferedImage.TYPE_INT_ARGB); + break; + case SCALE: + double a = (double) image.getWidth() / image.getHeight(); + double b = (double) (width * textWidth) / (height * textHeight); + assert (a > 0); + assert (b > 0); + + /* + System.err.println("Scale: original " + image.getWidth() + + "x" + image.getHeight()); + System.err.println(" screen " + (width * textWidth) + + "x" + (height * textHeight)); + System.err.println("A " + a + " B " + b); + */ + + if (a > b) { + // Horizontal letterbox + destWidth = width * textWidth; + destHeight = (int) (destWidth / a); + y = ((height * textHeight) - destHeight) / 2; + assert (y >= 0); + /* + System.err.println("Horizontal letterbox: " + destWidth + + "x" + destHeight + ", Y offset " + y); + */ + } else { + // Vertical letterbox + destHeight = height * textHeight; + destWidth = (int) (destHeight * a); + x = ((width * textWidth) - destWidth) / 2; + assert (x >= 0); + /* + System.err.println("Vertical letterbox: " + destWidth + + "x" + destHeight + ", X offset " + x); + */ + } + newImage = new BufferedImage(width * textWidth, height * textHeight, + BufferedImage.TYPE_INT_ARGB); + break; + } java.awt.Graphics gr = newImage.createGraphics(); - gr.drawImage(image, 0, 0, width, height, null); + if (scale == Scale.SCALE) { + gr.setColor(scaleBackColor); + gr.fillRect(0, 0, width * textWidth, height * textHeight); + } + gr.drawImage(image, x, y, destWidth, destHeight, null); gr.dispose(); - return newImage; } @@ -515,4 +788,44 @@ public class TImage extends TWidget { return newImage; } + // ------------------------------------------------------------------------ + // EditMenuUser ----------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Check if the cut menu item should be enabled. + * + * @return true if the cut menu item should be enabled + */ + public boolean isEditMenuCut() { + return false; + } + + /** + * Check if the copy menu item should be enabled. + * + * @return true if the copy menu item should be enabled + */ + public boolean isEditMenuCopy() { + return true; + } + + /** + * Check if the paste menu item should be enabled. + * + * @return true if the paste menu item should be enabled + */ + public boolean isEditMenuPaste() { + return false; + } + + /** + * Check if the clear menu item should be enabled. + * + * @return true if the clear menu item should be enabled + */ + public boolean isEditMenuClear() { + return false; + } + }