lib 1400179 // thumbnailator import net.coobird.thumbnailator.Thumbnailator; static class ImageSurface extends Surface { BufferedImage image; double zoomX = 1, zoomY = 1, zoomFactor = 1.5; private Rectangle selection; new L tools; O overlay; // voidfunc(Graphics2D) Runnable onSelectionChange; static bool verbose; bool noMinimumSize = true; S titleForUpload; O onZoom; bool specialPurposed; // true = don't show image changing commands in popup menu bool zoomable = true; bool noAlpha; // set to true to speed up drawing if you don't use alpha O interpolationMode = RenderingHints.VALUE_INTERPOLATION_BILINEAR; O onNewImage; BufferedImage imageToDraw; // if you want to draw a different image public ImageSurface() { this(dummyImage()); } static BufferedImage dummyImage() { ret new RGBImage(1, 1, new int[] { 0xFFFFFF }).getBufferedImage(); } *(MakesBufferedImage image) { this(image != null ? image.getBufferedImage() : dummyImage()); } *(BufferedImage image) { setImage(image); clearSurface = false; componentPopupMenu2(this, ImageSurface_popupMenuMaker()); new ImageSurfaceSelector(this); jHandleFileDrop(this, voidfunc(File f) { setImage(loadBufferedImage(f)) }); } public ImageSurface(RGBImage image, double zoom) { this(image); setZoom(zoom); } // point is already in image coordinates protected void fillPopupMenu(JPopupMenu menu, final Point point) { if (zoomable) { JMenuItem miZoomReset = new JMenuItem("Zoom 100%"); miZoomReset.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { setZoom(1.0); centerPoint(point); } }); menu.add(miZoomReset); JMenuItem miZoomIn = new JMenuItem("Zoom in"); miZoomIn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { zoomIn(zoomFactor); centerPoint(point); } }); menu.add(miZoomIn); JMenuItem miZoomOut = new JMenuItem("Zoom out"); miZoomOut.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { zoomOut(zoomFactor); centerPoint(point); } }); menu.add(miZoomOut); JMenuItem miZoomToWindow = new JMenuItem("Zoom to window"); miZoomToWindow.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { zoomToDisplaySize(); } }); menu.add(miZoomToWindow); addMenuItem(menu, "Show full screen", r { showFullScreen() }); addMenuItem(menu, "Point: " + point.x + "," + point.y + " (image: " + image.getWidth() + "*" + image.getHeight() + ")", null); menu.addSeparator(); } addMenuItem(menu, "Load image...", r { selectFile("Load image", voidfunc(File f) { setImage(loadImage2(f)) }) }); addMenuItem(menu, "Save image...", r { saveImage() }); addMenuItem(menu, "Upload image...", r { uploadTheImage() }); addMenuItem(menu, "Copy image to clipboard", r { copyImageToClipboard(getImage()) }); if (!specialPurposed) { addMenuItem(menu, "Paste image from clipboard", r { loadFromClipboard() }); addMenuItem(menu, "Load image snippet...", r { selectImageSnippet(voidfunc(S imageID) { setImage(loadImage2(imageID)) }); }); } if (selection != null) addMenuItem(menu, "Crop", r { crop() }); if (!specialPurposed) addMenuItem(menu, "No image", r { noImage() }); } void noImage() { setImage((BufferedImage) null); } void crop() { if (selection == null) ret; BufferedImage img = cloneClipBufferedImage(getImage(), selection); selection = null; setImage(img); } void loadFromClipboard() { BufferedImage img = getImageFromClipboard(); if (img != null) setImage(img); } void saveImage() { RGBImage image = new RGBImage(getImage(), null); JFileChooser fileChooser = new JFileChooser(getProgramDir()); if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) { try { image.save(fileChooser.getSelectedFile()); } catch (IOException e) { popup(e); } } } public void render(int w, int h, Graphics2D g) { if (verbose) _print("render"); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, interpolationMode); g.setColor(Color.white); BufferedImage image = or(imageToDraw, this.image); if (image == null) g.fillRect(0, 0, w, h); else { int iw = getZoomedWidth(), ih = getZoomedHeight(); bool alpha = !noAlpha && hasTransparency(image); if (alpha) g.fillRect(0, 0, w, h); if (eq(interpolationMode, 'thumbnailator)) g.drawImage(Thumbnailator.createThumbnail(image, iw, ih), 0, 0, null); else if (interpolationMode == RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR || zoomX >= 1 || zoomY >= 1) g.drawImage(image, 0, 0, iw, ih, null); else g.drawImage(resizeImage(image, iw, ih), 0, 0, null); // smoother if (!alpha) { g.fillRect(iw, 0, w-iw, h); g.fillRect(0, ih, iw, h-ih); } } if (overlay != null) { if (verbose) _print("render overlay"); pcallF(overlay, g); } if (selection != null) { if (verbose) _print("render selection"); // drawRect is inclusive, selection is exclusive, so... whatever, tests show it's cool. drawSelectionRect(g, selection, Color.green, Color.white); } } public void drawSelectionRect(Graphics2D g, Rectangle selection, Color green, Color white) { drawSelectionRect(g, selection, green, white, zoomX, zoomY); } public void drawSelectionRect(Graphics2D g, Rectangle selection, Color green, Color white, double zoomX, double zoomY) { g.setColor(green); int top = (int) (selection.y * zoomY); int bottom = (int) ((selection.y+selection.height) * zoomY); int left = (int) (selection.x * zoomX); int right = (int) ((selection.x+selection.width) * zoomX); g.drawRect(left-1, top-1, right-left+1, bottom-top+1); g.setColor(white); g.drawRect(left - 2, top - 2, right - left + 3, bottom - top + 3); } public ImageSurface setZoom(double zoom) { setZoom(zoom, zoom); this; } public void setZoom(double zoomX, double zoomY) { if (this.zoomX == zoomX && this.zoomY == zoomY) ret; if (verbose) _print("Setting zoom"); this.zoomX = zoomX; this.zoomY = zoomY; revalidate(); repaint(); centerPoint(new Point(getImage().getWidth()/2, getImage().getHeight()/2)); pcallF(onZoom); } public Dimension getMinimumSize() { if (noMinimumSize) ret new Dimension(1, 1); int w = getZoomedWidth(); int h = getZoomedHeight(); Dimension min = super.getMinimumSize(); return new Dimension(Math.max(w, min.width), Math.max(h, min.height)); } private int getZoomedHeight() { return (int) (image.getHeight() * zoomY); } private int getZoomedWidth() { return (int) (image.getWidth() * zoomX); } public void setImage(MakesBufferedImage image) { setImage(image.getBufferedImage()); } public void setImage(final BufferedImage img) { swing { BufferedImage newImage = img != null ? img : dummyImage(); BufferedImage oldImage = image; image = newImage; if (!imagesHaveSameSize(oldImage, newImage)) { if (verbose) _print("New image size"); revalidate(); // do we need this? } repaint(); pcallF(onNewImage); } } public BufferedImage getImage() { return image; } public double getZoomX() { return zoomX; } public double getZoomY() { return zoomY; } public Dimension getPreferredSize() { return new Dimension(getZoomedWidth(), getZoomedHeight()); } /** returns a scrollpane with the scroll-mode prevent-garbage-drawing fix applied */ public JScrollPane makeScrollPane() { JScrollPane scrollPane = new JScrollPane(this); scrollPane.getViewport().setScrollMode(JViewport.BACKINGSTORE_SCROLL_MODE); return scrollPane; } public void zoomToWindow() { zoomToDisplaySize(); } public void zoomToDisplaySize() swing { if (image == null) return; Dimension display = getDisplaySize(); double xRatio = (display.width-5)/(double) image.getWidth(); double yRatio = (display.height-5)/(double) image.getHeight(); setZoom(min(xRatio, yRatio)); revalidate(); } /** tricky magic to get parent scroll pane */ private Dimension getDisplaySize() { Container c = getParent(); while (c != null) { if (c instanceof JScrollPane) return c.getSize(); c = c.getParent(); } return getSize(); } public void setSelection(Rectangle r) { if (neq(selection, r)) { selection = r; pcallF(onSelectionChange); repaint(); } } public Rectangle getSelection() { return selection; } public RGBImage getRGBImage() { return new RGBImage(getImage()); } // p is in image coordinates void centerPoint(Point p) { JScrollPane sp = enclosingScrollPane(this); if (sp == null) ret; p = new Point((int) (p.x*getZoomX()), (int) (p.y*getZoomY())); final JViewport viewport = sp.getViewport(); Dimension viewSize = viewport.getExtentSize(); //_print("centerPoint " + p); int x = max(0, p.x-viewSize.width/2); int y = max(0, p.y-viewSize.height/2); //_print("centerPoint " + p + " => " + x + "/" + y); p = new Point(x,y); //_print("centerPoint " + p); final Point _p = p; awtLater(r { viewport.setViewPosition(_p); }); } Pt pointFromEvent(MouseEvent e) { ret pointFromComponentCoordinates(new Pt(e.getX(), e.getY())); } Pt pointFromComponentCoordinates(Pt p) { ret new Pt((int) (p.x/zoomX), (int) (p.y/zoomY)); } Pt pointToComponentCoordinates(double x, double y) { ret new Pt((int) (x*zoomX), (int) (y*zoomY)); } void uploadTheImage { call(hotwire(/*#1007313*/#1016427), "go", getImage(), titleForUpload); } void showFullScreen() { showFullScreenImageSurface(getImage()); } void zoomIn(double f) { setZoom(getZoomX()*f, getZoomY()*f); } void zoomOut(double f) { setZoom(getZoomX()/f, getZoomY()/f); } } // static function allows garbage collection static VF2 ImageSurface_popupMenuMaker() { ret voidfunc(ImageSurface is, JPopupMenu menu) { Point p = is.pointFromEvent(componentPopupMenu_mouseEvent.get()).getPoint(); is.fillPopupMenu(menu, p); }; }