Hi Alexander:

SunHints:

The text for the descriptions on the hints is still not updated to the text I 
asked for in a previous message.  Since there were multiple messages on that I 
thought I would paste the particular comment I was referring to:

- Descriptions for "on" and "off" should be something like "Use
resolution variants of images" and "Use only default resolution variants
of images" (and "Default resolution variant usage").  Most of the other
descriptions on hints are statements of what is going to happen or what
is going to be used rather than a simple 'the X state is Y'.

Reading this after the fact I realize that the word "default" is being used in 
two different ways in the descriptions I proposed.  Perhaps:

ON - Always use resolution-specific variants of images
OFF - Use only the standard resolution of an image
DEFAULT - Choose image resolutions based on a default heuristic

(perhaps add "(when available)" to ON?)

Note that I think you are still using the descriptions I asked for in the first 
quotation which I then revised because they were confusing.  Please use the 
latter descriptions (ON, OFF, DEFAULT above).

SG2D:

- If you rewrite getTransformedDestImageSize() to return the resolution variant 
instead of a Dimension then you avoid having to create a Dimension object that 
gets immediately tested for null again and/or consumed by the caller.  Just 
move the call to getResolutionVariant() inside the helper method and rename it.

- In getTransformedDestImageSize - dx,dy,sx,sy - should be named dw,dh,sw,sh?

- Also in getTransformedDestImageSize - the array of Point objects is 
problematic because they are used for the return values and that means early 
truncation/rounding, the methods that take arrays of doubles are generally less 
problematic in terms of garbage generation.  Also, you can use deltaTransform() 
because you don't need to worry about translation components since you only 
want the distance.  Finally, why not use the optimization I gave to just grab 
the matrix elements directly and do the calculations on them instead of 3 
separate point transforms which have predictable outcomes?  Here is the 
recommendation again:

And even for the case of GENERAL transform, the above can be simplified.  The 
tx.deltaTransform method in that case simply loads the array with:

{ m00, m10, m01, m11 }

and so the hypot methods are simply:

xScale = Math.hypot(tx.getScaleX(), tx.getShearY());
yScale = Math.hypot(tx.getShearX(), tx.getScaleY());

Multiply those values with dw,dh and you have your answer.  Another way to look 
at this is that your 3 transformed points were basically:

    { dx1 * m00 + dy1 * m01 + tx,  dx1 * m10 + dy1 * m11 + ty }
    { dx2 * m00 + dy1 * m01 + tx,  dx2 * m10 + dy1 * m11 + ty }
    { dx1 * m00 + dy2 * m01 + tx,  dx1 * m10 + dy2 * m11 + ty }

Subtracting [1] - [0] for the distance calculation eliminates a lot of terms 
and gives you:

    Math.hypot((dx2 - dx1) * m00, (dx2 - dx1) * m10)
==  (dx2 - dx1) * Math.hypot(m00, m10);
==  (dx2 - dx1) * Math.hypot(tx.getScaleX(), tx.getShearY());

Subtracting [2] - [0] also reduces:

    Math.hypot((dy2 - dy1) * m01, (dy2 - dy1) * m11);
==  (dy2 - dy1) * Math.hypot(m01, m11);
==  (dy2 - dy1) * Math.hypot(tx.getShearX(), tx.getScaleY());

- Also in getTransformeDestImageSize - the usage of the transform.getType() is 
off, some of the values are bitmasks so a <= comparison isn't the right 
comparison to use.  I'd use the following structure:

To check if it is "identity-ish", I'd use:
    ((type & ~(TYPE_TRANSLATION | TYPE_FLIP)) == 0)
To check for scale, use:
    ((type & ~(TYPE_TRANSLATION | TYPE_FLIP | TYPE_SCALE_MASK)) == 0)
and then the distance/hypot equations are in the final else as they are now.

- In calculating scaledWidth/width and the cast to float - even though I think the precedence order 
is on your side, I usually add extra parentheses to highlight the order so that people reading the 
code and/or modifying it don't mess it up.  My cutoff on what is obvious to a casual reader is 
usually "additive vs. multiplicative" and anything more subtle than that I use 
parentheses just in case someone comes along who is less familiar with the order and decides to 
interpose some additional operations that get in the way of the proper calculation.  I would have 
written "((float) sW) / w" just to make it obvious.

In LWCToolkit.java:

- there is a lot of string manipulation going on for every image fetch.  I 
guess this would only hurt those who use the Toolkit as their image storage 
(getting the image every time they need it), so I'm not going to worry about 
it, but it would be good to eventually get this integrated into the caching 
mechanism better so that a quick lookup of a previously loaded image could 
return faster.

One thing I did not examine very closely was the transfer of hash lookup and 
security code to the new utility class.  You should get a double-check from 
another engineer on that code.

Some more inline comments:

On 11/19/2013 5:06 AM, Alexander Scherbatiy wrote:

   Could you review the updated fix:
      http://cr.openjdk.java.net/~alexsch/8011059/webrev.08/
      - Some related to the ToolkitImage staff is moved from SunToolkit to 
ToolkitImageUtil

I'm not sure why this was done - it added a bunch of busy-work for approving 
the webrev that I don't have time for.  Hopefully someone else can verify the 
cut/paste and refactoring.

      - Preloading for the resolution variants is added

I'm not familiar with the ImageIcon, but it looks like the use of it for the 
resolution variant causes the resolution variant images to be aggressively 
kicked off immediately rather than as the image is used.  For an application 
that loads 1000 images and then only renders 10 of them, it will get 1000 
4xsized images (@2x have 4x pixels) loaded for 990 images that would never have 
been loaded previously.  Am I misreading how the ImageIcon is used there?

      The fix sets the resolution variant hint to default on Mac OS X and to 
off for other platforms now.

I'm still not sure why it isn't just left as DEFAULT on all platforms.  The 
other platforms don't even create resolution images anyway so their default is 
to just use the existing image which is fine.  We should not have platform 
issues affecting the default values for hints.

      The new logic uses the transform to get the destination image width and 
height.
      There is the trick question about the general transform type because 
image sides are not equal after the transformation in this case. So we could 
use only one side,
      or maximum value from opposite sides or may be an average value.

It looks like you are supplying the independent w,h and the default implementation of 
getResolutionVariant() is using "dispw <= imgw && disph <= imgh" which I think 
I asked Mike about and he said that was correct.  Is there another issue I'm missing?

The parameter passed to the getResVariant method is based on the subimage size, 
but the method treats it as if it were based on the full image size.  Either 
compute the scaled size of the full image, or inform the method of the 
sub-image being scaled, or simply pass in the scales, otherwise you will get 
the default image for any sub-image renderings of less than half of the 
original image even if they are rendered at retina scales.
    This is my fault. I really missed this part.  It is updated in the fix.

I think this is done in a fairly reasonable first approximation.  It works for 
the only case we have currently which is @2x images where the incoming integers 
translate into outgoing integers, but we'll have to revisit these exact 
calculations if we ever want to support non-integer-scale resolution variants 
(I think Win8 might have a number of non-integer scales).

     The algorithm that gets the scaled image size is based on the width and 
height of both images original and scaled. That means that both images should 
be already preloaded.
     In other case we need to draw the original image (because it is not 
possible to know the destination image size)  and wait for more drawImage(Image 
) calls to try to load the scaled image.

I don't like preloading them because we never preloaded the images previously 
and this will be a huge memory concern, not to mention the fact that we used to 
return nearly immediately from getImage() and now we have to load an entire (4x 
pixels) image to just return from that method.  This all needs to be done 
asynchronously.

Also, the code in getImageWithResolutionVariants() appears to wrap the image 
afresh every time it is called.  If you call it twice with a given string then 
the first time will make a regular image and store it there, then make a scaled 
image and replace it with the scaled image.  The second call will retrieve the 
scaled image left by the first call and then combine it with a second instance 
of the scaled image to make a new doubly-nested scaled image and store that in 
the hash.

     It seems that multi-resolution image preloading fixes image drawing issue 
as well as issue with the image observers. An image observer is not called from 
the g.drawImage()
     method in case if both images images are preloded and the original image 
does not have errors.

What about the other cases of errors?  What if they switch back and forth 
between the resolutions?  What image is reported in the callback for the 
non-scaled image?

What happens for MediaTracker?

     The fix does not use url.openConnection() now. It tries to preload the @2x 
image and if it is successful it preloads the original image as well.

getImage() is not supposed to preload anything.  Also, I didn't see where it 
preloads the original image, just the resolution variant.

    I also look at the custom cursor part of the HiDPI issues. A user needs to 
prepare the MultiResolutionImage and we can use 
CImage.createFromImages(List<Image> images)
    method to create necessary NSImage. This means that it should be possible 
to obtain all resolution variants from the MultiResolutionImage.
    I have included the "List<Image> getResolutionVariants()" method to the 
MultiResolutionImage for that.

I was going to ask about that new method.  Why does the developer need to 
manually intercede here?  They should be able to grab an image that has an @2x 
variant, use MediaTracker or some other mechanism to make sure it is loaded, 
then create the cursor and the code that creates the cursor should notice the 
resolution variations and deal with it on its own...

                        ...jim

Reply via email to