0

Copy and paste of images from web browser (Firefox, Chrome and Safari) stopped working on my OSX machine, it is ok on PC.

I tracked it down to the fact that I expected data with the flavor of image/x-java-image;class=java.awt.Image to always be a buffered image later in my code

I need it to be a BufferedImage so I can find the size of the image and access the data.

However now instead of it returning a BufferedImage it returns a sun.awt.image.MultiResolutionCachedImage, and to get the Buffered data I need to call getResolutionVariants which is only defined in the interface it implements sun.awt.image.MultiResolutionImage

So now my code has to refer directly to sun classes, surely this is wrong ?

   Image        image       = null;
   ImageCell    imageCell   = null;
   try
   {
        image  = (Image) trans.getTransferData(FileDropTarget.imageFlavor);
   }
   catch(Exception e)
   {
        MainWindow.logger.log(Level.WARNING,"Unable to extract image from drop data:"+e.getMessage(),e);
   }


   if(image!=null && image instanceof sun.awt.image.MultiResolutionImage)
   {
       for(Image i:mri.getResolutionVariants())
       {
           if(i instanceof BufferedImage)
           {
               ImageData imageData = new ImageData((BufferedImage) i, "downloaded:" + new Random().nextInt());
               imageCell = new ImageCell(imageData);
               return imageCell;
           }
       }
   }

The other thing I notice if I put some debugging is it always uses MultiResolutionCachedImage even when there is in fact only a single image !

It does seem these classes have been added to the java package in Java 9 but Im using Java 8.

Paul Taylor
  • 13,411
  • 42
  • 184
  • 351

1 Answers1

2

You’re looking into the wrong direction. Instead of adding another special case to your wrongly assumed special case, you should look for a solution that works for any Image, as the data flavor of image/x-java-image;class=java.awt.Image never guaranteed to deliver a specific type of image, hence the reference to the interface java.awt.Image

A general solution, based on how it is supposed to be handled since 1.1, but improved utilizing new Java features, is

public static BufferedImage getImage(Image image) {
    if(image instanceof BufferedImage) return (BufferedImage)image;
    Lock lock = new ReentrantLock();
    Condition size = lock.newCondition(), data = lock.newCondition();
    ImageObserver o = (img, infoflags, x, y, width, height) -> {
        lock.lock();
        try {
            if((infoflags&ImageObserver.ALLBITS)!=0) {
                size.signal();
                data.signal();
                return false;
            }
            if((infoflags&(ImageObserver.WIDTH|ImageObserver.HEIGHT))!=0)
                size.signal();
            return true;
        }
        finally { lock.unlock(); }
    };
    BufferedImage bi;
    lock.lock();
    try {
        int width, height=0;
        while( (width=image.getWidth(o))<0 || (height=image.getHeight(o))<0 )
            size.awaitUninterruptibly();
        bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = bi.createGraphics();
        try {
            g.setBackground(new Color(0, true));
            g.clearRect(0, 0, width, height);
            while(!g.drawImage(image, 0, 0, o)) data.awaitUninterruptibly();
        } finally { g.dispose(); }
    } finally { lock.unlock(); }
    return bi;
}

You may add other special cases, but you should always have the fallback handling arbitrary Image implementations.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Thanks I have a few points about this, firstly does it actually with MultiResolution images, secondly you are using quite low level (for java) code here - I would say my code is infinitently more readable, thirdly my main point was that it seemed wrong for jre to be using a non java public class and interface, dont you think that is wrong. – Paul Taylor Dec 09 '16 at 09:24
  • Java returns something which implements the `public` interface `java.awt.Image`, as it always did. Which is the only guaranteed property of something fulfilling the `image/x-java-image;class=java.awt.Image` mime type. It was always possible that another Java application pushes a custom `Image` implementation to the clipboard that is not converted to a `BufferedImage` when your application queries the clipboard. Your code might look simpler, but it is based on assumptions about implementation details. So you are accusing Java for its implementation details being…implementation specific details… – Holger Dec 09 '16 at 09:31
  • You *could* blame Java for not providing an easy-to-use “gimme the clipboard as `BufferedImage`” operation like `ImageIO` does for files, but well, here you have a generic “convert any `Image` to a `BufferedImage`” solution. Thinking about it, it might be possible to settle on an `InputStream` based mime type instead and pass it to `ImageIO` to get a `BufferedImage`. I never tried that. – Holger Dec 09 '16 at 09:38
  • Sorry I think you are missing my point a little, the copy is being done from Safari, Chrome and Firefox browsers, these are not Java applications, the conversion of whatever they put on the clipboard into what we take off with other Java application is being done by the jre OSX, in Java 7 it would return java.awt.image.BufferedImage, but now it returns sun.awt.image.MultiResolutionImage. On Windows it still returns BufferedImage, dont you think MultiResolutionImage shoud have been added to the public classes before they started returning it in jre – Paul Taylor Dec 09 '16 at 09:40
  • It doesn’t matter where the copy originates. A correct program reading the clipboard should not make any assumptions about who has filled the clipboard. It also should not make any assumptions about the type of the returned image, as the API does not specify any type. So it happens to be a `BufferedImage` on some platforms, this does not give you the right to claim that it *should* be `BufferedImage`. There is even no reason to assume that the actual `Image` implementation has to be `public`. There is the `public` `Image` type. That’s all that is specified. Nothing more. Since twenty years now – Holger Dec 09 '16 at 09:56
  • I'm not saying it should be a BufferedImage, I'm saying the OSX jre should not be returning a MultiResolutionImage when it s not a public class, there are no public interfaces to deal with it simply, they dont do this on PC and it only was one image so it is not really a multiple resolution image – Paul Taylor Dec 09 '16 at 10:12
  • A `MultiResolutionImage` still is a subclass of `Image`, isn’t it? – Holger Dec 09 '16 at 10:13
  • Sorry I meant MultiResolutionCachedImage, yes it is but its multiresolutioness is only visible via the interface sun.awt.image.MultiResolutionImage – Paul Taylor Dec 09 '16 at 10:25
  • So you’re complaining about a feature not available before still not being available (publicly)? – Holger Dec 09 '16 at 10:30
  • No Im complaining about them making undocumented change to the way the jdk interacts with the OS for no benefit, and only doing it for one OS. My code didn't blindy cast the transferdata to a BuffredImage it checked that is what is was, now sure you can say I'm doing it the wrong way but my point is that Oracle wrong to change the way java was working without a good reason to do so. – Paul Taylor Dec 09 '16 at 10:52
  • You still *might* get a benefit when passing the `Image` received from the clipboard to other APIs, e.g. rendering in an AWT component or passing it to an `ImageFilter` or printing it. Or when copying to the clipboard again. That’s the principle of abstraction and encapsulation, it’s not required that you can query any implementation detail. It’s your special requirement to have a `BufferedImage` that is not directly fulfilled. – Holger Dec 09 '16 at 10:58
  • Hmm, well it seems to me an Java api that requires you to use Rentrant locks, Graphics2D ectera just get an image from the clipboard is not fit for purpose, and I bet im not the only one to use the BufferedImage approach, I wonder how may other have had their code broken by this change. – Paul Taylor Dec 09 '16 at 11:12
  • This method fulfills the special requirement to have *a BufferedImage*. All operations mentioned above, painting, processing/filtering, printing, copying to clipboard, work *without* having a `BufferedImage`. You can do the same as my code by simply polling the query method. You can also use `MediaTracker`, which I avoided because it requires an existing AWT Component. You can also do the same using `synchronized` and `wait`/`notify`. I just used a more efficient approach. There’s also `PixelGrabber` to get the actual pixel data of an arbitrary `Image`, but it doesn’t return *a BufferedImage* – Holger Dec 09 '16 at 11:20
  • Okay but you are focussing on how to rewrite my code rather than the question which was why Oracle have made this change – Paul Taylor Dec 09 '16 at 12:00
  • [As said](http://stackoverflow.com/questions/41015956/why-does-copy-and-paste-of-images-on-osx-now-return-sun-awt-image-multiresolutio/41026422?noredirect=1#comment69324220_41026422), there *might* be use cases in which this has an advantage. Besides that, this question does not lead to anywhere. First, only Oracle can answer it, second, knowing their motivation won’t make your software any better. SO is for solving programming problems, not speculating about the intentions of other developers. – Holger Dec 09 '16 at 12:40
  • As a side note, even when `MultiResolutionImage` becomes `public` in Java 9, there still is no guaranty that its methods `getResolutionVariant[s]` return `BufferedImage`s. – Holger Dec 09 '16 at 12:42