Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

553
LINES

< > BotCompany Repo | #1004553 // ImageSurface

JavaX fragment (include) [tags: use-pretranspiled]

Libraryless. Click here for Pure Java version (19144L/119K).

sclass ImageSurface extends Surface {
  BufferedImage image;
  double zoomX = 1, zoomY = 1, zoomFactor = 1.5;
  private Rectangle selection;
  new L<AutoCloseable> tools;
  
  // use overlays now
  O overlay; // voidfunc(Graphics2D)
  L<G2Drawable> overlays = syncL();
  
  Runnable onSelectionChange;
  settable bool verbose;
  bool noMinimumSize = true;
  S titleForUpload;
  O onZoom;
  bool specialPurposed; // true = don't show image changing commands in popup menu
  settable bool allowPaste;
  settable 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; // use imageChanged event instead
  BufferedImage imageToDraw; // if you want to draw a different image
  File file; // where image was loaded from
  bool autoZoomToDisplay; // only works 100% when not in scrollpane
  settable bool repaintInThread; // after setImage, repaint in same thread
  BoolVar showingVar;
  
  Pt mousePosition;
  event mousePositionChanged;
  event imageChanged;
  event userModifiedImage(BufferedImage image);
 
  public ImageSurface() {
    this(dummyImage());
  }
  
  static BufferedImage dummyImage() {
    ret whiteImage(1);
  }
  
  *(File file) {
    setImage(file);
  }

  *(MakesBufferedImage image) {
    this(image != null ? image.getBufferedImage() : dummyImage());
  }
  
  *(BufferedImage image) {
    setImage(image);
    clearSurface = false;

    // perform auto-zoom when shown, resized or when parent resized
    bindToComponent(this, l0 performAutoZoom, null);
    onResize(this, l0 performAutoZoom);
    onEnclosingScrollPaneResize(this, l0 performAutoZoom);
    
    componentPopupMenu2(this, ImageSurface_popupMenuMaker());
    new ImageSurfaceSelector(this);
    
    jHandleFileDrop(this, voidfunc(File f) { setImage(loadBufferedImage(f)) });
    
    imageSurfaceOnHover(this, p -> {
      mousePosition = p;
      mousePositionChanged();
    });
  }

  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);
  
      menu.add(jMenuItemStayCheckedOnClick("Zoom to window",
        -> autoZoomToDisplay,
        -> setAutoZoomToDisplay(true)));
      
      addMenuItem(menu, "Show full screen", r { showFullScreen() });
      
      addMenuItem(menu, "Point: " + point.x + "," + point.y + " (image: " + w() + "*" + h() + ")", null);
  
      menu.addSeparator();
    }

    if (!specialPurposed)
      addMenuItem(menu, "Load image...", r { selectFile("Load image",
        voidfunc(File f) { setImage(loadImage2(f)) }) });
    addMenuItem(menu, "Save image...", r { saveImage() });
    ifdef ImageSurface_AllowUpload
      addMenuItem(menu, "Upload image...", r { uploadTheImage() });
    endifdef
    addMenuItem(menu, "Copy image to clipboard", r { copyImageToClipboard(getImage()) });
    if (!specialPurposed || allowPaste)
      addMenuItem(menu, "Paste image from clipboard", r { loadFromClipboard() });
      
    if (!specialPurposed)
      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;
    setUserModifiedImage(img);
  }
  
  void setUserModifiedImage(BufferedImage img) {
    setImage(img);
    userModifiedImage(img);
  }
  
  void loadFromClipboard() {
    BufferedImage img = getImageFromClipboard();
    if (img != null)
      setUserModifiedImage(img);
  }
  
  swappable File defaultImageDir() { ret getProgramDir(); }

  void saveImage() {
    var image = getImage();
    JFileChooser fileChooser = new JFileChooser(defaultImageDir());
    if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
      try {
        main saveImage(image, file = fileChooser.getSelectedFile());
      } catch e {
        popup(e);
      }
    }
  }
  
  void drawImageItself(int w, int h, Graphics2D g) {
    int iw = getZoomedWidth(), ih = getZoomedHeight();
    if (interpolationMode == RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR || zoomX >= 1 || zoomY >= 1) {
      // faster
      g.drawImage(image, 0, 0, iw, ih, null);
    } else
      g.drawImage(resizeImage(image, iw, ih), 0, 0, null); // smoother
  }

  swappable void drawBackground(int w, int h, Graphics2D g) {
    g.setColor(or(getBackground(), Color.white));
    g.fillRect(0, 0, w, h);
  }

  public void render(int w, int h, Graphics2D g) {
    if (verbose) _print("render");
    g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, interpolationMode);
    drawBackground(w, h, g);
    BufferedImage image = or(imageToDraw, this.image);
    if (!hasImage())
      drawBackground(w, h, g);
    else {
      bool alpha = !noAlpha && hasTransparency(image);
      if (alpha)
        drawBackground(w, h, g);
    
      drawImageItself(w, h, g);
      
      int iw = getZoomedWidth(), ih = getZoomedHeight();
      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);
    }
    
    for (var overlay : cloneList(overlays)) pcall {
      overlay.drawOn(cloneGraphics(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) {
    autoZoomToDisplay = false;
    setZoom_dontChangeAutoZoom(zoomX, zoomY);
  }
  
  public void setZoom_dontChangeAutoZoom(double zoomX, double zoomY default zoomX) {
    if (this.zoomX == zoomX && this.zoomY == zoomY) ret;
    if (verbose) _print("Setting zoom");
    this.zoomX = zoomX;
    this.zoomY = zoomY;
    revalidateMe();
    repaint();
    centerPoint(new Point(getImage().getWidth()/2, getImage().getHeight()/2));

    pcallF(onZoom);
  }

  public scaffolded Dimension getMinimumSize() {
    if (noMinimumSize) ret new Dimension(1, 1);
    int w = getZoomedWidth();
    int h = getZoomedHeight();
    Dimension min = super.getMinimumSize();
    ret printIfScaffoldingEnabled(this, new Dimension(Math.max(w, min.width), Math.max(h, min.height)));
  }

  int getZoomedHeight() {
    return (int) (h() * zoomY);
  }

  int getZoomedWidth() {
    return (int) (w() * zoomX);
  }

  bool isShowing_quick() {
    if (showingVar == null) swing {
      showingVar if null = componentShowingVar(ImageSurface.this);
    }
    
    ret showingVar!;
  }
  
  public void setImageIfShowing_thisThread(BufferedImage etc image) {
    if (isShowing_quick())
      setImage_thisThread(image);
  }
  
  void setImage(File file) {
    setFile(file);
    setImage(loadImage2(file));
  }

  public void setImage(MakesBufferedImage image) swing {
    setImage_thisThread(image);
  }
  
  public void setImage(BufferedImage img) swing {
    setImage_thisThread(img);
  }
    
  public void setImage_thisThread(BufferedImage etc img) {
    BufferedImage newImage = img != null ? img : dummyImage();
    BufferedImage oldImage = image;
    image = newImage;
    if (verbose) print("Old image size:" + imageSize(oldImage) + ", new image size: " + imageSize(newImage));
    bool sameSize = imagesHaveSameSize(oldImage, newImage);
    if (!sameSize) {
      if (verbose) _print("New image size");
      revalidateMe();
    }
    quickRepaint();
    pcallF(onNewImage);
    if (!sameSize && autoZoomToDisplay) zoomToDisplaySize();
    imageChanged();
  }
  
  void setImageAndZoomToDisplay(BufferedImage img) {
    setImage(img);
    zoomToDisplaySize();
  }

  public BufferedImage getImage() {
    return image;
  }

  public double getZoomX() {
    return zoomX;
  }

  public double getZoomY() {
    return zoomY;
  }

  public scaffolded Dimension getPreferredSize() {
    ret printIfScaffoldingEnabled(this, 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 (!hasImage()) return;
    Dimension display = getDisplaySize();
    if (display.width == 0 || display.height == 0) ret;
    int w = w(), h = h();
    double xRatio = (display.width-5)/(double) w;
    double yRatio = (display.height-5)/(double) h;
    if (scaffoldingEnabled(this))
      printVars zoomToDisplaySize(+display, +w, +h, +xRatio, +yRatio);
    setZoom_dontChangeAutoZoom(min(xRatio, yRatio));
    revalidateMe();
  }

  /** tricky magic to get parent scroll pane */
  private scaffolded Dimension getDisplaySize() {
    Container c = getParent();
    while (c != null) {
      if (c instanceof JScrollPane)
        return c.getSize();
      c = c.getParent();
    }
    return getSize();
  }
  
  public void setSelection(Rect r) {
    setSelection(toRectangle(r));
  }

  public void setSelection(Rectangle r) {
    if (neq(selection, r)) {
      selection = r;
      pcallF(onSelectionChange);
      quickRepaint();
    }
  }

  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);
    ifdef ImageSurface_AllowUpload
      var img = getImage();
      JTextField tf = jTextField(titleForUpload);
      showFormTitled("Upload Image (PNG)",
        "Image title (optional)", tf, func {
          disableSubmitButton(getFrame(tf));
          thread "Upload Image" {
            try {
              messageBox("Image uploaded as " + uploadPNGToImageServer(img, titleForUpload = getTextTrim(tf)));
              disposeFrame(tf);
            } catch e {
              enableSubmitButton(getFrame(tf));
              messageBox(e);
            }
          }
          false;
        });
    endifdef
  }
  
  void showFullScreen() {
    showFullScreenImageSurface(getImage());
  }
  
  void zoomIn(double f) { setZoom(getZoomX()*f, getZoomY()*f); }
  void zoomOut(double f) { setZoom(getZoomX()/f, getZoomY()/f); }
  
  selfType setFile(File f) { file = f; this; }
  
  void setOverlay(IVF1<Graphics2D> overlay) {
    this.overlay = overlay;
  }
  
  bool hasImage() { ret image != null; }
  public int w() { ret image.getWidth(); }
  public int h() { ret image.getHeight(); }
  
  void setPixelated aka pixelate(bool b) {
    assertTrue(b);
    imageSurface_pixelated(this);
  }
  
  selfType setAutoZoomToDisplay aka autoZoomToDisplay(bool b) {
    if (autoZoomToDisplay = b)
      zoomToDisplaySize();
    this;
  }
  
  void quickRepaint() {
    if (repaintInThread)
      paintImmediately(0, 0, getWidth(), getHeight());
    else
      repaint();
  }
  
  void setTool(ImageSurfaceMouseHandler tool) swing {
    removeAllTools();
    addTool(tool);
  }
  
  bool hasTool(AutoCloseable tool) {
    ret swing(-> tools.contains(tool));
  }
  
  void addTool(ImageSurfaceMouseHandler tool) swing {
    if (!tools.contains(tool))
      tool.register(this);
  }
  
  void removeTool(AutoCloseable tool) swing {
    if (tools.contains(tool)) {
      close(tool);
      tools.remove(tool);
    }
  }
  
  void removeAllTools aka clearTools() {
    closeAllAndClear(tools);
  }
  
  void performAutoZoom {
    if (autoZoomToDisplay) zoomToDisplaySize();
  }
  
  void revalidateMe() {
    revalidateIncludingFullCenterContainer(this);
  }
  
  void addOverlay(G2Drawable overlay) {
    overlays.add(overlay);
    repaint();
  }
  
  void clearOverlays() {
    if (nempty(overlays)) {
      overlays.clear();
      repaint();
    }
  }
  
  void setOverlay(G2Drawable overlay) {
    clearOverlays();
    if (overlay != null) addOverlay(overlay);
  }
  
  void loadImage(File f) {
    setImage(loadImage2(f));
  }
  
  JComponent visualize() {
    ret jscroll_center_borderless(this);
  }
  
  void standardZoom() {
    setZoom(1.0);
  }
  
  !include #1034645 // print
} // end of ImageSurface

// static function allows garbage collection 
static VF2<ImageSurface, JPopupMenu> ImageSurface_popupMenuMaker() {
  ret voidfunc(ImageSurface is, JPopupMenu menu) {
    Point p = is.pointFromEvent(componentPopupMenu_mouseEvent.get()).getPoint();
    is.fillPopupMenu(menu, p);
  };
}

download  show line numbers  debug dex  old transpilations   

Travelled to 20 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, cfunsshuasjs, ddnzoavkxhuk, gwrvuhgaqvyk, irmadwmeruwu, ishqpsrjomds, jtubtzbbkimh, lpdgvwnxivlt, mqqgnosmbjvj, onxytkatvevr, ppjhyzlbdabe, pyentgdyhuwx, pzhvpgtvlbxg, sawdedvomwva, tslmcundralx, tvejysmllsmz, vouqrxazstgt, xrpafgyirdlv

No comments. add comment

Snippet ID: #1004553
Snippet name: ImageSurface
Eternal ID of this version: #1004553/163
Text MD5: d1e8e48ee0a64fb49de058409c417d27
Transpilation MD5: 68579e37079dfbc7cb009eb4284f2520
Author: stefan
Category: javax / gui
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2022-08-23 18:20:57
Source code size: 16129 bytes / 553 lines
Pitched / IR pitched: No / No
Views / Downloads: 1331 / 7434
Version history: 162 change(s)
Referenced in: [show references]