Just friendly remainder.

 Thanks,
 Alexandr.


On 7/8/2014 7:02 PM, Alexander Scherbatiy wrote:


  Hi Phil,

  Could you review the fix?

  Thanks,
  Alexandr.


On 6/11/2014 7:18 PM, Alexander Scherbatiy wrote:

 Hi Phil ,

I just prepared a simple FAQ about the Custom MultiResolution image API. Hope it will be helpful.

1. Scale naming convention for high-resolution images.

Different OSes use different "scale" naming convention for high-resolution images:
 Mac OS X: image.ext, im...@2x.ext
 Windows: image.scale-100.ext, image.scale-140.ext, image.scale-180.ext

 Q: Does "scale" naming convention supported in JDK?
A: Mac OS X "scale" naming convention are supported in JDK 8u20 (see JDK-8011059) It is planned to support the Windows "scale" naming convention as well.

 Q. How does it work in JDK?
A. Bundle image.ext and im...@2x.ext images with your app on Mac OS X and call Toolkit.getImage(...) method:
        Image image = Toolkit.getDefaultToolkit().getImage("image.ext");
        Graphics2D g2d = // get graphics
        g2d.drawImage(image, 0, 0, null)
SunGraphics2D automatically queries and draws the provided high-resolution image.

Q: There are different "scale" naming conventions on Mac OS X and Windows. May be it is better to have unified "scale" naming conventions for all OSes in Java like image[java-scale-Nx].ext?
  A: It seems reasonable and can be filled as a new JDK enhancement.

  Q: Does using "scale" naming conventions solves all problems.
A: There are tasks like image processing/programmatically generated images/loading images from non-standard sources
       that can't be solved with predefined set of images.
  Q: Are there any tools that support these tasks?
A: Cocoa API contains NSImage that allows to work with image representations: addRepresentation/removeRepresentation/representations JDK uses these methods to get/set multi-resolution images for the Native system (see sun.lwawt.macosx.CImage class).

2. Graphics2D
Q: How SunGraphics2D deals with multi-resolution images?
A: SunGraphics2D queries a resolution variant using DPI scale factors and transformed base image sizes
       //   logicalDPIX, logicalDPIY - DPI scale factors
// destImageWidth, destImageHeight - transformed base image sizes including DPI scale factors multiResolutionImage.getResolutionVariant(logicalDPIX, logicalDPIY, destImageWidth, destImageHeight);

Q: Which algorithm multi-resolution image is used in getResolutionVariant(...) method? A: ToolkitImage returned by toolkit.loadImage() method should behave like the native system. It means that it should use transformed image sizes on Mac OS X and only DPI scale factors on Windows.
     it looks like:
        -----------------
       //   logicalDPIX, logicalDPIY - DPI scale factors
// destImageWidth, destImageHeight - transformed base image sizes including DPI scale factors public Image getResolutionVariant(float logicalDPIX, float logicalDPIY,
                float destImageWidth, float destImageHeight) {
                  if (Mac OS X) {
return resolution variant best fitted to the destImageWidth and destImageHeight
                  } else if (Windows){
return resolution variant best fitted to the logicalDPIX and logicalDPIY scale factors
                  }
        }
        -----------------

 3. Custom multi-resolution image.
Q: The custom multi-resolution image should be able to return an image according to the requested
      transformed image size and DPI scale factors. Is it enough?
A: There are task like setting custom cursor that require to get all resolution variants. So the custom multi-resolution image should also contain the getResolutionVariants():

 Q: Should the custom multi-resolution image be class or interface?
 A: There is ToolkitImage that should also have resolution variants.
   It is not possible to extend it from MultiResolutionImage class.
The current proposal introduces the MultiResolutionImage as an interface.

 Q: MultiResolutionImage interface sounds strange for me.
 A: The better name can be suggested.

  Q: What does the Custom MultiResolution image API suggest?
A: The current proposal provides MultiResolutionImage interface with the following methods:
    ---------------------------
    Image getResolutionVariant(float logicalDPIX, float logicalDPIY,
            float destImageWidth, float destImageHeight);

    List<Image> getResolutionVariants();
    ---------------------------
  and AbstractMultiResolutionImage class. See samples below.


 4. Memory cost
 Q: Can the the implementation be "lazy"?
A: SunGraphics2D does not require full list of resolution variants. It queries only the image with necessary resolution.
     It means that resolution variants can be loaded by demand.
   Setting a custom cursor requires all resolution variants.

 5. Rendering hints.
 Q: Why rendering hints are added.
A: Provided rendering hints affects only multi-resolution images and allows to disable resolution variants usage in app. It can be useful for performance reasons.

 6. Samples.
 Q: It is interesting to look at samples.
 A: Below are 3 samples:
     1. Draw an image with "Hello World!" text
     2. Set a lightened custom cursor
     3. Draw a multi-resolution image created from the program

Sample 1. Draw a image with "Hello World!" text. The text is drawn both on the base image and on high-resolution image.
       disk: duke.png, d...@2x.png
   -------------------------------
    public static void main(String[] args) {

Image image = Toolkit.getDefaultToolkit().getImage("duke.png"); // duke.png and d...@2x.png images are loaded by MR-ToolkitImage

        Image imagewithText = image instanceof MultiResolutionImage
                ? new TextMultiresolutionImage(image) : drawText(image);

        Graphics2D g2d = // get graphics 2D
        g2d.drawImage(imagewithText, x, y, null);
    }

    static Image drawText(Image image) {
        // return an image with "Hello World!" text
    }

static class TextMultiresolutionImage extends AbstractMultiResolutionImage {

        private final Image baseImage;

        public TextMultiresolutionImage(Image baseImage) {
            this.baseImage = baseImage;
        }

        @Override
public Image getResolutionVariant(float logicalDPIX, float logicalDPIY,
                float destImageWidth, float destImageHeight) {
            Image rvImage = ((MultiResolutionImage) baseImage).
                    getResolutionVariant(logicalDPIX, logicalDPIY,
                            destImageWidth, destImageHeight);
            return drawText(rvImage);
        }

        @Override
        public List<Image> getResolutionVariants() {
// this method is not used by SunGraphics2D to draw the image.
              // we just skip it in this example
        }

        @Override
        protected Image getBaseImage() {
            return drawText(baseImage);
        }
    }
   -------------------------------

  Sample 2. Using filters to create a lightened  custom cursor.
  The filter is applied to both the base and high-resolution image.
   -------------------------------
    public static void main(String[] args) {

Image image = Toolkit.getDefaultToolkit().getImage("cursor.png"); // cursor.png and cur...@2x.png files are provided
        Image lightenedImage = image instanceof MultiResolutionImage
? new LigtenedMultiresolutionImage(image) : applyFilter(image);

        Cursor lightenedCursor = Toolkit.getDefaultToolkit().
createCustomCursor(lightenedImage, new Point(0, 0), "Lightened Cursor");
        JFrame frame = new JFrame("Frame with lightened cursor");
        frame.setCursor(lightenedCursor);
    }

    static Image applyFilter(Image image) {
        GrayFilter filter = new GrayFilter(true, 50);
final ImageProducer prod = new FilteredImageSource(image.getSource(), filter);
        return Toolkit.getDefaultToolkit().createImage(prod);
    }

static class LigtenedMultiresolutionImage extends AbstractMultiResolutionImage {

        private final Image baseImage;

        public LigtenedMultiresolutionImage(Image baseImage) {
            this.baseImage = baseImage;
        }

        @Override
public Image getResolutionVariant(float logicalDPIX, float logicalDPIY,
                float destImageWidth, float destImageHeight) {
// this method is not necessary for the custom cursor creation
             // we just skip it
        }

// all resolution variants are created to pass them to NSImage for the custom cursor on Mac OS X.
        @Override
        public List<Image> getResolutionVariants() {
            List<Image> resolutionVariants = new LinkedList<>();
            for (Image rvImage : ((MultiResolutionImage) baseImage).
                    getResolutionVariants()) {
                resolutionVariants.add(applyFilter(rvImage));
            }
            return resolutionVariants;
        }

        @Override
        protected Image getBaseImage() {
            return applyFilter(baseImage);
        }
    }
   -------------------------------

   Sample 3. Draw a multi-resolution image created from the program:
   -------------------------------
    public static void main(String[] args) {

        Image image = generateImage(1);
        Image image2x = generateImage(2);
        Image mrImage = new CustomMultiresolutionImage(image, image2x);

        Graphics2D g2d = // get graphics2D
        g2d.drawImage(mrImage, 0, 0, null);
    }

    static Image generateImage(float scaleFactor) {
        // generate image according to the scale factor
    }

static class CustomMultiresolutionImage extends AbstractMultiResolutionImage {

        private final Image image;
        private final Image highResolutionImage;

public CustomMultiresolutionImage(Image baseImage, Image highResolutionImage) {
            this.image = baseImage;
            this.highResolutionImage = highResolutionImage;
        }

        @Override
public Image getResolutionVariant(float logicalDPIX, float logicalDPIY,
                float destImageWidth, float destImageHeight) {
// destImageWidth and destImageHeight includes both transforms
            // DPI scale factors from Graphics
            if (destImageWidth <= image.getWidth(null)
                    && destImageHeight <= image.getHeight(null)) {
                return image;
            }
            return highResolutionImage;
        }

        @Override
        public List<Image> getResolutionVariants() {
            return Arrays.<Image>asList(image, highResolutionImage);
        }

        @Override
        protected Image getBaseImage() {
            return image;
        }
    }
-------------------------------
Thanks,
Alexandr.


On 6/10/2014 6:37 PM, Alexander Scherbatiy wrote:
On 6/10/2014 1:07 AM, Phil Race wrote:
Why the split ?
If you look only at the first part. If you can do that then why is the 2nd part needed ?
The second part introduces algorithms that can be used to retrieve a resolution variant from a set of images. It can be DPI based, transform based, OS based and so on.
   The first part can be implemented without the second part.

The name "MultiResolutionImage" implies to me that this is a sub-class of Image. But its not, its a way to get images. AbstractMultiResolutionImage, however is
a subclass and it implements the former.

Could you suggest the better name? It really needs to have an interface if existed image implementation is supposed to have resolution variants. The example which is used in JDK is ToolkitImage. Toolkit.getImage(filename) method returns ToolkitImage which is loaded by demand. LWCToolkit should return an image with resolution variants on Mac OS X if both image and image@2x are provided. What we need here is the ToolkitImage that contains resolution variants. It can be done if the MultiResolutionImage is an interface and it is not possible to do if MultiResolutionImage is a class.
   Here is the MultiResolutionToolkitImage implementation:
http://hg.openjdk.java.net/jdk9/client/jdk/file/b7ef5e2d252c/src/share/classes/sun/awt/image/MultiResolutionToolkitImage.java

I am supposing (since you don't explain) that you want an Image sub-class here so that the app can specify it where ever an Image is currently accepted by API
and the API that is "aware" can accept it.
If an image implements the MultiResolutionImage interface, SunGraphics2D can use it
    to draw an image with necessary resolution on HiDPI display.

I worry about the memory cost of all of this. Can the the implementation be "lazy"?
    Yes. See the MultiResolutionCachedImage implementation:
http://hg.openjdk.java.net/jdk9/client/jdk/file/b7ef5e2d252c/src/share/classes/sun/awt/image/MultiResolutionCachedImage.java
ie even if I call getResolutionVariants() do those images all have to be fully initialised before
they are used? It looks like the app probably has to do so ..
If it needs to pass resolution variants to the native system like setting a custom cursor on Mac OS X
    it really needs to initialize all resolution variants.

If it needs to create one multi-resolution image based on another multi-resolution image like generating a lightening image using a filter, it possible to do this lazy. See the map(Function<Image, Image> mapper) method in the MultiResolutionCachedImage.

SunGraphics2D class uses only getResolutionVariant( ...) method to retrieve necessary resolution variant. It does not call getResolutionVariants() methods so all resolution variants are not created during image drawing.


Also it precludes being able to return "on demand" an image that is rendered to be exactly the size requested. That could be created, drawn using graphics primitives
and created precisely and only if needed.

Instead we have an API that requires you to apparentlty eagerly create even the
highest res image when you are on a device that has no need for it.

Who will actually call getResolutionVariants() ?
    Both.
Is it us (the implementation) because we
We use it to create an NSImage from a custom cursor. See Toolkit.createCustomCursor()
   and CImage.createFromImage(final Image image) methods.

Developers can use it to show all resolution variants in some image tool.

don't trust the app to make the right selection based on the parameterised call
getResolutionVariant() ?
As it shown, the getResolutionVariant(...) and getResolutionVariants() methods are used
   for different purposes.
getResolutionVariant(...) method is used by SunGraphics2D class to pickup an image
   with necessary resolution variant.
getResolutionVariants() method is used when an application needs to use all resolution variants.


Which approach do we use to pick the image ? If its the former, the app controls it,
    It is the former.
We also use it MR-ToolkitImage to get a resolution variant according to the current system (for example, transforms
     are included to get resolution variant size on Mac OS X).

if its the latter its us. But which ?

I am still stuck on the parameters to getResolutionVariant

 * @param baseImageWidth the width of the base image.


Isn't the base image always the smallest ?
No for general case. May be it would be possible to print a multi-resolution image
    on a printer that can have low DPI.

Why are we, the caller, supposed
to say what that size to the class that has that image.

This question has already had long discussion. The answer is that we do it because it is free for us. SunGraphics2D already gets the base image size because it uses it for resolution image size calculation. If you have objections against this, let's remove the base image size parameters. Developer always can obtain this information calling getWidth()/Height() methods.

So I'd really like to see the example of that method in CustomMultiResolutionImage
filled out so we can see what is imagined here ..

   Below are two samples.
The first one loads a multi-resolution image from disk, and writes text "Hello World!" on it. Only getResolutionVariant(...) method is used by system in SunGraphics2D. The getResolutionVariants() method is not used.

The second one creates a lightened custom cursor. The getResolutionVariants() method is called by system to create NSImage with necessary image representations.

Note that Toolkit.getImage(filename) method is already able to load both image and image@2x images on Mac OS X.

   Sample 1. Draw an image with "Hello World!" text:
       disk: duke.png, d...@2x.png
   -------------------------------
    public static void main(String[] args) {

Image image = Toolkit.getDefaultToolkit().getImage("duke.png"); // duke.png and d...@2x.png images are loaded by MR-ToolkitImage

        Image imagewithText = image instanceof MultiResolutionImage
? new TextMultiresolutionImage(image) : drawText(image);

        Graphics2D g2d = // get graphics 2D
        g2d.drawImage(imagewithText, x, y, null);
    }

    static Image drawText(Image image) {
        // return an image with "Hello World!" text
    }

static class TextMultiresolutionImage extends AbstractMultiResolutionImage {

        private final Image baseImage;

        public TextMultiresolutionImage(Image baseImage) {
            this.baseImage = baseImage;
        }

        @Override
public Image getResolutionVariant(float destImageWidth, float destImageHeight) {
            Image rvImage = ((MultiResolutionImage) baseImage).
getResolutionVariant(destImageWidth, destImageHeight);
            return drawText(rvImage);
        }

        // this method is not used by SunGraphics2D to draw the image
        @Override
        public List<Image> getResolutionVariants() {
            List<Image> resolutionvariants = new LinkedList<>();
            for (Image image : ((MultiResolutionImage) baseImage).
                    getResolutionVariants()) {
                resolutionvariants.add(drawText(image));
            }
            return resolutionvariants;
        }

        @Override
        protected Image getBaseImage() {
            return drawText(baseImage);
        }
    }
   -------------------------------

  Sample 2. Using filters to create a lightened  custom cursor.
   -------------------------------
    public static void main(String[] args) {

Image image = Toolkit.getDefaultToolkit().getImage("cursor.png"); // cursor.png and cur...@2x.png files are provided
        Image lightenedImage = image instanceof MultiResolutionImage
? new LigtenedMultiresolutionImage(image) : applyFilter(image);

        Cursor lightenedCursor = Toolkit.getDefaultToolkit().
createCustomCursor(lightenedImage, new Point(0, 0), "Lightened Cursor");
        JFrame frame = new JFrame("Frame with lightened cursor");
        frame.setCursor(lightenedCursor);
    }

    static Image applyFilter(Image image) {
        // apply a filter to create ligtened image
    }

static class LigtenedMultiresolutionImage extends AbstractMultiResolutionImage {

        private final Image baseImage;

        public LigtenedMultiresolutionImage(Image baseImage) {
            this.baseImage = baseImage;
        }

        @Override
public Image getResolutionVariant(float destImageWidth, float destImageHeight) {
            Image rvImage = ((MultiResolutionImage) baseImage).
getResolutionVariant(destImageWidth, destImageHeight);
            return applyFilter(rvImage);
        }

        // all resolution variants are created to pass them to NSImage
        @Override
        public List<Image> getResolutionVariants() {
            List<Image> resolutionvariants = new LinkedList<>();
            for (Image image : ((MultiResolutionImage) baseImage).
                    getResolutionVariants()) {
                resolutionvariants.add(applyFilter(image));
            }
            return resolutionvariants;
        }

        @Override
        protected Image getBaseImage() {
            return applyFilter(baseImage);
        }
    }
   -------------------------------

Based solely on the usage I see here, its not clear why MultiResolutionImage needs to separately exist. what would implement MultiResolutionImage except for a class that extends AbstractMultiResolutionImage ? Where would you use
a straight implementation of MultiResolutionImage ?
See sun.awt.image.MultiResolutionToolkitImage in JDK 9. Both ToolkitImage and MultiResolutionImage should be used in this case.


Actually I am not sure you answered Jim's question as to who is requesting these APIs.
"The AWT team" doesn't need them as they won't be writing the apps.

There was a long thread about the image with sub-pixel resolution drawing on Mac OS X: http://mail.openjdk.java.net/pipermail/macosx-port-dev/2013-April/005559.html

It was pointed out that Icon images that can be programmatically generated also need to have HiDPI support: http://mail.openjdk.java.net/pipermail/macosx-port-dev/2013-April/005566.html http://mail.openjdk.java.net/pipermail/macosx-port-dev/2013-April/005569.html

All requests about Mac OS X HiDPI support were included to the umbrella issue:
   7124410 [macosx] Lion HiDPI support
https://bugs.openjdk.java.net/browse/JDK-7124410


If the 99% use case will be to provide a way for apps to provide images at custom sizes then we seem to be making them write new code. SFAIK FX found a way to do something
similar to what OS X and Windows do which is to load based on file
name convention.
JDK 8 have already loaded images with @2x name convention on Mac OS X. See the fix for the issue JDK-8011059 [macosx] Support automatic @2x images loading on Mac OS X
       https://bugs.openjdk.java.net/browse/JDK-8011059
If we can do that, we load just the one we need. Is the point
of use so far removed from the loading logic that we can't do this ?

Mac OS X has both ways to create images: using @2x name convention for files and NSImage with methods addRepresentation/removeRepresentation/representations.

The current API is proposed to dial with images that can have source that is different from files.
    It is also used to process already loaded images.
See the provided two samples with lightened custom cursor and text on image.
    Is it possible to write the same samples on JavaFX?

And none of this seems to help anyone who calls new BufferedImage(w, h, type) ..

Yes. It needs to create a BufferedImage for each screen resolution and put them to a multi-resolution image.


BTW I am not sold on the need for the RenderingHint. Where did the idea come from ? It would affect all rendering using that graphics instance, not just a specific image and if someone doesn't want a MultiRes image used, then maybe they just don't provide one ..

KEY_RESOLUTION_VARIANT is used to switch on/off resolution variants usage. VALUE_RESOLUTION_VARIANT_ON - SunGraphics2D queries resolution variants from multi-resolution image on HiDPI displays. VALUE_RESOLUTION_VARIANT_OFF - SunGraphics2D does not use resolution variants. Only base image is used.

In any case, without a solid demonstrated need I would not add the API.

    See provided 2 samples.

  Thanks,
  Alexandr.

-phil.

On 6/4/2014 7:29 AM, Alexander Scherbatiy wrote:

  Hi Phil,

Could you review the fix where only new MultiResolutionImage interface and AbstractMultiResolutionImage class are added:
http://cr.openjdk.java.net/~alexsch/8029339/webrev.05/

  Thanks,
  Alexandr.


On 5/19/2014 2:46 PM, Alexander Scherbatiy wrote:

  Hi Phil,

On 5/16/2014 9:12 PM, Phil Race wrote:
I think Jim was looking at this. I am not sure if you yet answered all his questions/concerns.

There's a lot of API here and it will take more time than I have right now just to get
my head around it so do not expect a quick answer.

1. Why is there no javadoc on the new API on Toolkit ?
  It was decided to split the original issue on two parts:
- this fix adds only MultiResolutionImage interface and AbstractMultiResolutionImage class. Here is the webrev for it: http://cr.openjdk.java.net/~alexsch/8029339/webrev.05/
      - the Toolkit related API is moved to the separate issue

  Could you review the current fix:
http://cr.openjdk.java.net/~alexsch/8029339/webrev.05/

2. What kinds of classes are expected to implement MultiResolutionImage
Application ones or platform ones ?
    Both.
- Application: A developer can provide a set of images with different resolutions to create a multi-resolution image. An image with best-fitting resolution
        will be drawn on HiDPI display.
    - Platform: we used it to support Aqua L&F on HiDPI displays.

3. can you better explain all these parameters :

49 * @param logicalDPIX the logical horizontal DPI of the desktop. 50 * @param logicalDPIY the logical vertical DPI of the desktop.
  51      * @param baseImageWidth the width of the base image.
  52      * @param baseImageHeight the height of the base image.
53 * @param destImageWidth the width of the destination image. 54 * @param destImageHeight the height of the destination image.
  55      * @return image resolution variant.

    Could we postpone it to the CCC request?


4.    public List<Image> getResolutionVariants();

So this implies a fixed, known ahead of time set of images ?
Why is it required to have this API ? How will anyone be able to
tell which is which and use the list ?

   Here are some usages from the JDK code:
- AquaImagefactory.getAppIconCompositedOn(final Image background) The original multi-resolution image is used to create another multi-resolution image with the background - AquaUtils.generateLightenedImage(Image image, ImageFilter filter) The original multi-resolution image is used to create lightening multi-resolution image
    - CImage.createFromImage(final Image image)
Resolution variants from a multi-resolution image are used to create an NSImage - CCustomCursor: it is possible set a custom cursor which contains resolution variants to the native system

Usually the getResolutionVariants() method is used to create one multi-resolution image based on the another multi-resolution image.

5. Why is the rendering hint needed ?
Someone can manually switch off the multi-resolution image drawing from graphics so only the base image will be drawn. It is useful for the performance reason. There is a choice to draw the high-resolution image slowly or the low-resolution image faster.

   Thanks,
   Alexandr.
-phil.


On 5/16/2014 9:16 AM, Alexander Scherbatiy wrote:

  Hi Phil,

I need a reviewer from the 2d group for the fix. Could you take a look at the fix and review it?

  Thanks,
  Alexandr.


On 5/12/2014 6:35 PM, Alexander Scherbatiy wrote:

There was a long thread about the image with sub-pixel resolution drawing on Mac OS X: http://mail.openjdk.java.net/pipermail/macosx-port-dev/2013-April/005559.html

It was pointed out that Icon images that can be programmatically generated also need to have HiDPI support: http://mail.openjdk.java.net/pipermail/macosx-port-dev/2013-April/005566.html http://mail.openjdk.java.net/pipermail/macosx-port-dev/2013-April/005569.html

All requests about Mac OS X HiDPI support were included to the umbrella issue:
   7124410 [macosx] Lion HiDPI support
https://bugs.openjdk.java.net/browse/JDK-7124410

 Thanks,
 Alexandr.

On 4/25/2014 6:45 PM, Alexander Scherbatiy wrote:
On 4/25/2014 2:17 AM, Jim Graham wrote:
Hi Alexandr,

I asked for who was requesting these facilities and you responded with the solution you are planning to provide.

I don't care what the solution looks like if we have nobody asking for the feature - I am asking who is asking for these capabilities?

   This is the request from the AWT team for the HiDPI support.

   Thanks,
   Alexandr.

            ...jim

On 4/4/14 4:53 AM, Alexander Scherbatiy wrote:
On 4/3/2014 2:23 AM, Jim Graham wrote:
Hi Alexandr,

The back and forth is getting confusing here, so I thought I'd try to
summarize and start fresh(ish):

1. We need to support @2x internally for MacOS compatibility (done).

2. We will need to support _DPI images for Win-DPI compatibility (TBD).

3. Customers may have their own collection of images to bundle together into an MR image (working on that here). What is the push
for this?  Is this simply parity with Mac interfaces?

         ----------
Image[] resolutionVariants = // get sorted by sizes array of
resolution variants;
         Image mrImage =
Toolkit.getDefaultToolkit().createMRImage(baseImageIndex,
resolutionVariants);
         ----------

      Here is the proposed patch:
http://cr.openjdk.java.net/~alexsch/8029339/webrev.04/

4. Customers may want to synthetically generate images at arbitrary resolutions (a variation that is impacting this solution). What is
the push for this?
         ----------
         Image mrImage =
Toolkit.getDefaultToolkit().createMRImage(baseImageWidth, baseImageHeight, new float[][]{{100, 100}, {150, 150}, {200, 200}}, //
resolution variants sizes
(rvWidth, rvHeight) -> { /* generate a resolution
variant */  });
        ----------


5. I'm guessing that customers might want to override the logic to choose from among multiple resolutions. That came from me based on seeing Mac and Win using different selection logic and our history of developers split between those wanting cross-platform consistency and those wanting consistency with native apps on each platform. Also,
the needs of an animator may differ from the needs of a
resolution-settable-document editor as to how dynamically the images
shift between resolution variants.
        ----------
Image[] resolutionVariants = // get sorted by sizes array of
resolution variants;
         Image mrImage = ImageResolutionHelper.createMRImage(
(rvWidth, rvHeight, resolutionVariants) -> { /*use a custom logic to choose a resolution variant from an array of images*/},
                 (logicalDPI, baseImageSize, destImageSize) ->
destImageSize, // calculate the custom aware resolution variant size
                 baseImageIndex, resolutionVariants);
        ----------

or just extend the CustomMultiResolutionImage which has Image as the
parent class:

--------------------
  public class CustomMultiResolutionImage extends
AbstractMultiResolutionImage {

     @Override
public Image getResolutionVariant(float logicalDPIX, float
logicalDPIY,
             float baseImageWidth, float baseImageHeight,
             float destImageWidth, float destImageHeight) {
// return a resolution variant based on the given logical DPI,
         // base image size, or destination image size
     }

     @Override
     public List<Image> getResolutionVariants() {
         // return a list of resolution variants
     }

     @Override
     protected Image getBaseImage() {
         // return the base image
     }
}
--------------------

Is that a fair summary of all of the considerations so far, or did I
miss something?
    I think it should cover the main needs.

     Thanks,
     Alexandr.

            ...jim

On 3/27/14 7:43 AM, Alexander Scherbatiy wrote:

  Below are some thoughts about TK.createMRImage(...) method

On 3/24/2014 4:52 PM, Alexander Scherbatiy wrote:
Hello,

  Could you review the updated fix:
http://cr.openjdk.java.net/~alexsch/8029339/webrev.03/

  - baseImageWidth/Height arguments are added to the
getResolutionVariant(...) method
  - dest image sizes are reverted to included DPI scale
- AbstractMultiResolutionImage is added. It needs only to implement
only 3 methods from the AbstractMultiResolutionImage class
    to create a custom multi-resolution image. For example:

On 3/22/2014 3:57 AM, Jim Graham wrote:

Your code example below can be expressed as an implementation of the single-method, lambda-compatible interface that expresses just the
getRV() method. They could easily do:

final Image baseImage = ...;
TK.createMRImage(new RVInterface() {
    public Image getRV(...) {
        // calculate rvWidth and rvHeight
// look up rvWidth/rvHeight in a database of images
        // possibly contruct a new image
        return rvImage;
    }
}, baseImage);

The RVInterface mixes the logic that construct an image and
chooses the necessary resolution variant.
It is ok if a developer always implements this interface. If it needs to have DPI/Transform/Platform aware RVInterface the image
construction logic should be separated.

Does TK.createMRImage() method implies that Platform aware logic
should be used for a resolution-variant choosing?
If so, may be general createMRImage() can be placed in the
ImageResolutionHelper.
The main issue I see is if you might want the newly constructed variants to appear in the List returned from the getVariants() method. I'm not sure what value that would have beyond simply returning the base media that the object uses from which to construct
its variants...?

It can be solved by using something like array of image sizes or other seeds and a mapper that can create an image from the given seed.

  It can look like:
-------------------------
public class ImageResolutionHelper {
     public interface RVChooser {

         public Image getRV(
                 float logicalDPIX, float logicalDPIY,
float baseImageWidth, float baseImageHeight, float destImageWidth, float destImageHeight,
                 final Image... resolutionVariants);
     }

     public static final RVChooser DPI_AWARE = ...;
     public static final RVChooser TRANSFORM_AWARE = ...;

// resolutionVariants is an array of sorted by width/height images
     static Image createMRImage(final RVChooser rvChooser,
             final int baseImageIndex, final Image...
resolutionVariants) { ... }

// sorted by width/height images should be generated from seeds static <Type> Image createMRImage(final RVChooser rvChooser, final Type baseImageSeed, final Function<Type, Image>
mapper, final Type... rvSeeds) {...}
}

public abstract class Toolkit {
public abstract Image createMRImage(int baseImageIndex, Image...
resolutionVariants); // Platform aware rv chooser is used
     public abstract RVChooser getPlatformRVChooser() ;
}
--------------------------
Thanks,
Alexandr.


I think it is better to provide both the MultiResolutionImage
and
its implementation based on the given resolution variants array.

It occurs to me that even if we don't go with a lambda-factory-based approach like what I'm describing, it might make sense to provide a baseMR implementation that they can subclass to keep them from trying to subclass off of BufferedImage instead. I really would like to avoid "custom MR images are subclasses of BufImg" if we can as I
think the mix of concepts is a little jarring...

            ...jim

    The implementation could look like:
---------------------------------
public class CustomMultiResolutionImage extends Image implements
MultiResolutionImage {

     int baseImageIndex;
     Image[] resolutionVariants;

public CustomMultiResolutionImage(int baseImageIndex,
             Image... resolutionVariants) {
         this.baseImageIndex = baseImageIndex;
         this.resolutionVariants = resolutionVariants;
     }

     @Override
     public int getWidth(ImageObserver observer) {
         return getBaseImage().getWidth(null);
     }

     @Override
     public int getHeight(ImageObserver observer) {
         return getBaseImage().getHeight(null);
     }

     @Override
     public ImageProducer getSource() {
         return getBaseImage().getSource();
     }

     @Override
     public Graphics getGraphics() {
         return getBaseImage().getGraphics();
     }

     @Override
public Object getProperty(String name, ImageObserver observer) { return getBaseImage().getProperty(name, observer);
     }

     @Override
public Image getResolutionVariant(float logicalDPIX, float
logicalDPIY,
             float destinationImageWidth, float
destinationImageHeight) {
             // calculate resolution variant width/height
         return getResolutionVariant(rvWidth, rvHeight);
     }

     @Override
     public List<Image> getResolutionVariants() {
         return Arrays.asList(resolutionVariants);
     }

private Image getResolutionVariant(float rvWidth, float
rvHeight) {
// return a resolution variant based on the given width and
height
     }

     private Image getBaseImage() {
         return resolutionVariants[baseImageIndex];
     }
}
---------------------------------

   Thanks,
   Alexandr.


Then we provide one of these from TK.get/createImage() when the
platform detects @2x, or Win8-style variants.

For custom images we provide TK.createMRImage(lambda getRV, Image
variants...) and TK.createMRImage(Image variants...);

Since the get<List> method is just bookkeeping, I don't see them needing to override it, so the getRV() method is really the only
thing
they might want to override, and we can tie into the new Lambda capabilities by making a single-method interface for it that they
supply in a factory method.

I realize that the interface you created is more fundamentally
OO, but
the Image class has always been special in this regard in the AWT ecosystem (in so far as we do not support someone implementing their own Image subclass even though it is technically possible).
Because of
this special nature of Image, we end up with the situation that if someone were given a need to create a subclass of Image, then they would turn to BufImg as their superclass even though BufImg is essentially an implementation-specific leaf node on the Image class hierarchy. This approach with a factory method to create an
internal
subclass of the new MRI class mirrors the existing cases of Image
objects that come from factories as well.

Thoughts?

            ...jim


On 3/20/14 7:52 AM, Alexander Scherbatiy wrote:

   Hello,

   Could you review the updated version of the fix:
http://cr.openjdk.java.net/~alexsch/8029339/webrev.01/

- The "getResolutionVariant(int width, int height)" method from
MultiResolutionImage class is changed to
    Image getResolutionVariant(float logicalDPIX, float
logicalDPIY,
float width, float height, AffineTransform transform);

- sun.awt.image.ImageResolutionHelper class is added. The
sun.awt.image.MultiResolutionToolkitImage and
sun.awt.image.MultiResolutionBufferedImage classes are used
PLATFORM ImageResolutionHelper.

The MultiResolutionImage interface implementation could look
like:
------------------------------
public class CustomMultiResolutionImage extends BufferedImage
implements
MultiResolutionImage {

     private final Image[] resolutionVariants;

public CustomMultiResolutionImage(int baseIndex, Image...
images) {
super(images[baseIndex].getWidth(null),
images[baseIndex].getHeight(null),
BufferedImage.TYPE_INT_RGB);
this.resolutionVariants = images;
         Graphics g = getGraphics();
g.drawImage(images[baseIndex], 0, 0, null);
         g.dispose();
     }

     @Override
public Image getResolutionVariant(float logicalDPIX, float
logicalDPIY,
             float width, float height, AffineTransform
transform) {
return getResolutionVariant(logicalDPIX * width,
logicalDPIY *
height);
     }

     @Override
     public List<Image> getResolutionVariants() {
         return Arrays.asList(resolutionVariants);
     }

public Image getResolutionVariant(double width, double
height) {
         for (Image image : resolutionVariants) {
if (width <= image.getWidth(null) && height <=
image.getHeight(null)) {
                 return image;
             }
         }
         return this;
     }
}
------------------------------


   Thanks,
   Alexandr.

On 2/27/2014 4:54 PM, Alexander Scherbatiy wrote:
On 2/22/2014 3:54 AM, Jim Graham wrote:
Hi Alexandr,

On 2/18/14 7:33 AM, Alexander Scherbatiy wrote:
Hi Jim,

  Let's divide the discussion into two part.

  1. Where it is better to hold resolution variants?
Putting resolution variants in Image class brings some
questions like:
- Some type of images do not need to have resolution variants - Should resolution variants have the same type as the base
image?
- getResolutionVariants() method can return copy of the
original
list
so add/removeRV methods should be also added.

There are pros and cons for placing resolution variants to
Image
class or to a separate intreface.

I agree that this could be a separate interface. In my examples below I was just sticking them inside an "Image{}" to show where
they
lived in the set of involved objects, not a specific
recommendation
that they actually be new methods on the base class itself. I
probably should have put a comment there about that.

With respect to add/remove - that is assuming a need for manual construction of an image set, right? Forgive me if I'm
forgetting
something, but I seem to recall that manual Multi-Res images was proposed as a way for developers to introduce @2x support
themselves,
but if we are internally managing @2x and -DPI variants for them, then I'm not sure if there is actual developer need to manually
construct their own.  Am I forgetting something?
The NSImage has addRepresentation/removeRepresentation
methods to
work with image representations on Mac OS X.
   The java.awt.Image class should provide similar
functionality to
have the possibilities as Cocoa on HiDPI displays.


2. Using scale factor/image sizes/scaled image sizes to
retreive a
resolution variant.

May be it is better to have a structure that provide all
necessary
information to query the resolution variant: scale factor,
draw area
width/height, transformed area width/height?

   For example:
   ---------------------
     public interface MultiResolutionImage {

         interface DrawAreaInfo {

             float getScaleFactor();
             float getAreaWidth();
             float getAreaHeight();
             float getTransformedAreaWidth();
             float getTransformedAreaHeight();
         }

public Image getResolutionVariant(DrawAreaInfo
drawAreaInfo) ;
         public List<Image> getResolutionVariants();
     }
   ---------------------

The problem with a constructor is that this is something that is (potentially) done on every drawImage() call, which means we are inviting GC into the equation. If we can come up with a simple
"just
a couple/3/4 numbers" way to embed that data into a method call
argument list then we can make this lighter weight.

What about simply having floating point (double) dimensions on
the
rendered size
There should be a way to choose a resolution variant
based on
requested drawing size or transformed drawing size.
At least a current transformation should be included too.
plus a single floating point "logical DPI" for the screen?
There is the ID2D1Factory::GetDesktopDpi method which returns
dpiX and dpiY.
http://msdn.microsoft.com/en-us/library/windows/apps/dd371316

That means that logicalDPIX/Y can have different values.
     At least it is described in the
http://msdn.microsoft.com/en-us/library/windows/apps/ff684173 "To get the DPI setting, call the ID2D1Factory::GetDesktopDpi method. The DPI is returned as two floating-point values, one for
the
x-axis and one for the y-axis. In theory, these values can differ.
Calculate a separate scaling factor for each axis."

  The getResolutionVariant method could look like:
--------------------------------------
public Image getResolutionVariant(float logicalDPIX, float
logicalDPIY,
float widthX, float widthY, AffineTransform
transform);
--------------------------------------

If the image is known (either passed as an argument or the
method is
called on the image), then it can provide the original WH.

The MultiResolutionImage default implementation could allow
to use
different strategies like scale factor/transfom/OS based to query a resolution variant. The OS based strategy can be
used by
default.

For Mac policy, all we need is the transformed dimensions, which
can
be passed in as FP for generality. For Windows policy, all we
need
is logical DPI for the screen. What other information would we need, or would an algorithm like to use, that can't be computed
from
those 2 pieces?
The aim is to provide a base class that can be used to
create a
MultiResolutionImage like:
http://hg.openjdk.java.net/jdk9/client/jdk/diff/ae53ebce5fa3/src/share/classes/sun/awt/image/MultiResolutionBufferedImage.java





A developer should be able to implement a custom algorithm to
query a resolution variant.

It can be done by overriding the getResolutionVariant image:
   -----------------------
Image mrImage = new MultiResolutionBufferedImage(){
            @Override
            public Image getResolutionVariant(...) {
                // Custom logic here
            }
        };
   -----------------------

Or it can be done by using resolution variant choosers so a developer can implement custom resolution variant query:
   -----------------------
public class MultiResolutionBufferedImage implements
MultiResolutionImage{

    interface ResolutionVariantChooser{
Image getResolutionVariant(dpi, size,..., List<Image>
resolutionVariants);
    }
    ResolutionVariantChooser TRANSFORM_BASED = null;
    ResolutionVariantChooser DPI_BASED = null;

    ResolutionVariantChooser rvChooser;

    @Override
public Image getResolutionVariant(dpi, size,...,) { return rvChooser.getResolutionVariant(dpi, size,...,
getResolutionVariants());
    }
}
   -----------------------

  Thanks,
  Alexandr.


            ...jim

Thanks,
Alexandr.


On 2/13/2014 4:42 AM, Jim Graham wrote:
On 2/12/14 5:59 AM, Alexander Scherbatiy wrote:
On 2/8/2014 4:19 AM, Jim Graham wrote:
The primary thing that I was concerned about was the
presence of
integers in the API when Windows uses non-integer multiples
It would make sense to pass real numbers to the getResolutionVariant() method if the difference between
resolution
variants sizes is 1.
      It seems that it is not a common case.

I was thinking of other API that is related to this, such as
the API
that queries the scaling factor from a SurfaceManager. I
seem to
remember some integer return values in that, but Windows might
have
the answer 1.4 or 1.8, depending on the screen scaling factor
that was
determined from the UI.

In terms of the getResolutionVariant() method here, those
non-integer
screen scaling factors don't directly impact this API. But, we
have
some issues with the use of integers there from other sources:

- That API assumes that the caller will determine the pixel
size
needed, but the actual media choice is determined with
different
techniques on Mac and Windows so this means that the caller
will
have
to worry about platform conventions. Is that the right
tradeoff?

- The technique recommended for Mac involves computing the
precise
size desired using the current transform, which may be a
floating
point value, so the integer values used in this API are already approximations and there is no documentation on how to
generate the
proper integer. In particular, the current code in SG2D
naively
uses
a cast to integer to determine the values to supply which
means a
transformed size of W+0.5 will be truncated to W and the lower resolution image will be used. Does that conform to Mac
guidelines? Do
they require the truncated size to reach W+1 before the next
size is
used? Passing in float or double values would sidestep all of
that
since then the comparisons would be done with full precision,
but as
long as we can determine a "best practices compatible with all platforms" rule on how to round to integers, then integers
are OK
there.

- The Windows document you cite below suggests that the
determination
should be made by looking at the Screen DPI and choosing the
next
higher media variant based on that screen DPI. They do not
specify
choosing media based on the current transform as is done for
Mac.  If
we stick with supplying values that are used to determine which
media
to use, then on Windows we should not take the transform into
account,
but instead query the SurfaceManager for the scale factor and
only
transform by those values (even if the current transform was
manually
overridden to identity).

There are pros and cons to both approaches.

Mac ensures that you are always using the best media for any
given
render operation.

But, Windows ensure more consistency in the face of other
scaling.

The thing to consider is that if you have a 500x500 image
with a
1000x1000 variant and you rendering it at 500x500 and then
501x501,
that first jump will be fairly jarring as the scaled version
of the
1000x1000 will not look precisely like the original 500x500
did.
With
@2x images only, this effect is minimized so the advantage of
always
using "the best media for a given render operation" may
outweigh the
inconsistency issue. But, on Windows where the media are
1.4x or
1.8x
in size, a downscaled image will start to show more
interpolation
noise and so the balance of the two choices may shift more
towards not
wanting a jarring shift.

We might want one or more of the following:

- Developer chooses policy (TX_AWARE, DPI_AWARE,
ALWAYS_LARGEST,
NONE,
PLATFORM) where the last policy would use TX_AWARE on Mac and
DPI_AWARE on Windows

- We create our own policy and always use it (TX_AWARE? or
DPI_AWARE?)

- We create our own policy that dynamically chooses one of the
above
strategies depending on platform or available media or ???

- We could create an optional interface for them to install
their
own
algorithm as well. I think it would work best as a delegate
interface
that one installs into Image so that it can be used with any
image
without having to subclass (it wouldn't really have much to do
for
BufferedImages or VolatileImages, though):

class Image {
void setResolutionHelper(ImageResolutionHelper foo);
    List<Image> getResolutionVariants();
}

or:

class Graphics {
void setResolutionHelper(ImageResolutionHelper foo);
}

or - anywhere else it could be installed more centrally (per
App
context)?

and the interface would be something like one of these
variants:

interface ImageResolutionHelper {
// This version would prevent substituting a random image: // They have to return an index into the List<Image> for
that
image...
public int chooseVariant(Image img, double dpi, number w,
number h);

or:

// This version would allow substituting an arbitrary
image:
public Image getVariant(Image img, double dpi, number w,
number
h);
}

Since they would be in full control of the policy, though, we
would
unfortunately always have to call this, there would be no more
testing
in SG2D to see "if" we need to deal with DPI, though perhaps we
could
document some internal conditions in which we do not call it
for
common cases (but that would have to be well agreed not to
get in
the
way of reasonable uses of the API and well documented)?

Note that we would have to do a security audit to make sure
that
random image substitution couldn't allow any sort of "screen
phishing"
exploit.

            ...jim

and also what policy they use for choosing scaled images.

I don't see a mention of taking the current transform into
account,
just physical issues like screen DPI and form factor. They
talk
about
resolution plateaus and in their recommendations section they
tell the
developer to use a particular property that tells them the
screen
resolution to figure out which image to load if they are
loading
manually. There is no discussion about dynamically loading
multiple
versions of the image based on a dynamic program-applied
transform
factor as is done on MacOS.

Also, they tell developers to draw images to a specific size
rather
than using auto-sizing. That begs the question as to how
they
interpret a call to draw an image just using a location in
the
presence of various DPI factors.
There is an interesting doc that describes how to write
DPI-aware
Win32 applications:
http://msdn.microsoft.com/en-us/library/dd464646%28v=vs.85%29.aspx


It is suggested to handle WM_DPICHANGED message, load
the
graphic
that has slightly greater resolution to the current DPI and
use
StretchBlt
      to scale down the image.

     Thanks,
     Alexandr.


            ...jim

On 2/7/14 3:00 AM, Alexander Scherbatiy wrote:
On 1/22/2014 6:40 AM, Jim Graham wrote:
Hi Alexander,

Before we get too far down the road on this API, I think we
understand
the way in which MacOS processes multi-resolution images
for
HiDPI
screens, but have we investigated the processes that
Windows
uses
under Windows 8? My impression is that Windows 8 has
included a
number of new techniques for dealing with the high
resolution
displays
that it will run on in the Windows tablet and mobile
industries
and
that these will also come into play as 4K displays (already
available)
become more common on the desktop. We should make sure
that
what we
come up with here can provide native compatibility with
either
platform's policies and standard practices.

If you've investigated the MS policies I'd like to see a
summary so
that we can consider them as we review this API...
There is the Windows Guidelines for scaling to pixel
density:
http://msdn.microsoft.com/en-us/library/windows/apps/hh465362.aspx



which says that Windows has automatic resource loading
that
supports
three version of images scaling (100%, 140%, and 180%)
--------------------------------
Without scaling, as the pixel density of a display device
increases, the
physical sizes of objects on screen get smaller.
When UI would otherwise be too small to touch and when text
gets
too
small to read,
Windows scales the system and app UI to one of the following
scaling
plateaus:

     1.0 (100%, no scaling is applied)
     1.4 (140% scaling)
     1.8 (180% scaling)

Windows determines which scaling plateau to use based on the
physical
screen size, the screen resolution, the DPI of the
screen, and
form
factor.

Use resource loading for bitmap images in the app package
For
bitmap
images stored
in the app package, provide a separate image for each
scaling
factor(100%, 140%, and 180%),
and name your image files using the "scale" naming
convention
described
below.
Windows loads the right image for the current scale
automatically.
--------------------------------

The image name convention for the various scales is:
images/logo.scale-100.png
images/logo.scale-140.png
images/logo.scale-180.png

The 'ms-appx:///images/logo.png' uri is used to load the
image
in an
application.

If we want to support this in the same way as it is done
for Mac
OS X
the WToolkit should return MultiResolution image in
case if
the
loaded image has .scale-* qualifiers.
The Graphics class can request an image with necessary
resolution
from the MultiResolution image.

It seems that nothing should be changed in the
MultiResolution
interface in this case.

    Thanks,
    Alexandr.

...jim

On 1/14/14 2:54 AM, Alexander Scherbatiy wrote:

Hello,

Could you review the fix:
bug: https://bugs.openjdk.java.net/browse/JDK-8029339
   webrev:
http://cr.openjdk.java.net/~alexsch/8029339/webrev.00

This is a proposal to introduce an API that allows to
create a
custom
multi resolution image.

I. It seems reasonable that the API should provide two
basic
operations:

1. Get the resolution variant based on the requested
image
width and
height:
- Image getResolutionVariant(int width, int height)

Usually the system provides the scale factor which
represents
the
number of pixels corresponding to each linear unit on the
display.
However, it has sense to combine the scale factor and
the
current
transformations to get the actual image size to be
displayed.

  2. Get all provided resolution variants:
    - List<Image> getResolutionVariants()

   There are several uses cases:
- Create a new multi-resolution image based on the
given
multi-resolution image.
- Pass to the native system the multi-resolution
image. For
example,
a use can set to the system the custom multi-resolution
cursor.

II. There are some possible ways where the new API can be
added

  1. java.awt.Image.

The 2 new methods can be added to the Image class. A
user
can
override
the getResolutionVariant() and getResolutionVariants()
methods to
provide the resolution variants
or there can be default implementations of these
methods
if a
user
puts resolution variants
   to the list in the sorted order.

To check that the image has resolution variants the
following
statement can be used:
image.getResolutionVariants().size()
!= 1

The disadvantage is that there is an overhead that the
Image
class
should contain the List object and not all
   images can have resolution variants.

2. Introduce new MultiResolutionImage interface.

A user should extend Image class and implement the
MultiResolutionImage interface.

   For example:
---------------------
public class CustomMultiResolutionImage extends
BufferedImage
implements MultiResolutionImage {

         Image highResolutionImage;

public CustomMultiResolutionImage(BufferedImage
baseImage,
BufferedImage highResolutionImage) {
super(baseImage.getWidth(),
baseImage.getHeight(),
baseImage.getType());
this.highResolutionImage = highResolutionImage;
Graphics g = getGraphics();
g.drawImage(baseImage, 0, 0, null);
g.dispose();
         }

@Override
public Image getResolutionVariant(int width, int
height) {
return ((width <= getWidth() && height <=
getHeight()))
? this : highResolutionImage;
         }

@Override
public List<Image> getResolutionVariants() {
return Arrays.asList(this,
highResolutionImage);
         }
     }
---------------------

The current fix adds the MultiResolutionImage interface
and
public
resolution variant rendering hints.

Thanks,
Alexandr.





















Reply via email to