Skip to content

Move ImageData Scaling Impl to Image for all platforms #1933

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

amartya4256
Copy link
Contributor

This PR contributes to fixing the implementation of Smooth scaling of the ImageData to get rid of the rounding errors because of multiple scale ups and downs with fractional scale factor. The commit moves and modifies the DPIUtil::autoScaleImageData method implementation in ImageData class to adapt the same.

contributes to
#62 and #127

Copy link
Contributor

github-actions bot commented Mar 24, 2025

Test Results

   539 files  + 6     539 suites  +6   21m 45s ⏱️ -37s
 4 330 tests +37   4 320 ✅ +35  10 💤 +3  0 ❌  - 1 
16 407 runs  +37  16 309 ✅ +35  98 💤 +3  0 ❌  - 1 

Results for commit 15c999a. ± Comparison against base commit c7712e4.

♻️ This comment has been updated with latest results.

*
* @since 3.130
*/
public ImageData scaleToUsingSmoothScaling(Device device, int width, int height) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about completely integrating the functionality into ImageData, i.e., having this also applied when calling ImageData#scaledTo()? That's missing anyway (no one outside SWT can retrieve a smooth-scaled image as the functionality is internal to DPIUtil).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to pass device to scaleToUsingSmoothScaling and internalizing it to scaledTo method means we need to do some API change because ImageData has no device context. Any suggestions?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any suggestions?

Anything that needs a device does not belong to ImageData, this class is explicitly for carry device independent data.

As the method creates an image internally anyways, why not put such functionality into the Image class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same has been done in the previous PR: #1913
But it replicates the code from DPIUtil a little bit.

Copy link
Contributor

@laeubi laeubi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we add API it needs to be there for all platforms, as all calls are effectively boils down to DPI util, why not put the method there?

public ImageData scaleToUsingSmoothScaling(Device device, int width, int height) {
Image original = new Image (device, (ImageDataProvider) zoom -> this);
/* Create a 24 bit image data with alpha channel */
final ImageData resultData = new ImageData (width, height, 24, new PaletteData (0xFF, 0xFF00, 0xFF0000));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about other bit depth?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ImageData is a part of common so it is indeed available for all the paltform. This PR just moves the whole smooth scaling block from DPIUtil to ImageData to make it able to call the GC.drawImage which takes pixels as an input so that we can avoid scaling up and scaling down leading to rounding error.

As per the previous implementation, it is always using 24 bits hence I didn't modify anything in the logic. The sole objective of this PR is to get rid of the rounding error through this refactoring.

*
* @since 3.130
*/
public ImageData scaleToUsingSmoothScaling(Device device, int width, int height) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming we have such method in Image class

Suggested change
public ImageData scaleToUsingSmoothScaling(Device device, int width, int height) {
public Image scaleTo(int width, int height) {
  1. it could be implemented on all OS
  2. would not need to care about creating a new image or what device to use
  3. caller can decide if the image is to be used directly, or ImageData is to be extracted

This might also be useful outside of DPI use-case.

Copy link
Contributor Author

@amartya4256 amartya4256 Mar 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's been a misunderstanding. Initially there was no method called scaleToUsingSmoothScaling in Image. It was added via a previous PR last week: #1913
Following the merge, @HeikoKlare mentioned that it doesn't seem like a good design since we replicate the code and then the idea was to move the whole imagedata scaling block from DPIUtil to ImageData class itself and hence this PR reverts that commit and moves the block of code from DPIUtil to ImageData. And since ImageData is in the common package, it seems to be way more cleaner than the previous solution.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can see the method is currently private in Image, this one is public in ImageData and it has many problems:

  1. It does not belong to ImageData ... whenever one wants to produces ImageData it should go through an image or create the ImageData based on some other source
  2. It seem to only be used/useful for Win32
  3. It has a bad naming for a public method (might be arguable) and I'm not sure if "smooth scaling" is commonly understood. For a public method I would expect it to be name scale(...) any maybe accepting some flags that control the technique (e.g Using Antialias and so on)

So my suggestion would be to:

  1. make a public method in Image, this also has the advantage that any platform specific optimization might be used, it also removes the need for some code / parameters and the caller does not need to create a new image (again) if it should be used as such.
  2. name it more general scale and let it accept some SWT flags, so one can later enhance the method without the need for new API, for now it might only support SWT.SMOOTH or even SWT.DEFAULT to let the implementation decide the best algorithm to use.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also moving this to Image might not be feasible since Cursor also uses this method in DPIUtil and that doesn't involve any Image. Refer: Cursor:win32_getHandle

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also moving this to Image might not be feasible since Cursor also uses this method in DPIUtil and that doesn't involve any Image

DPI Util can of course call code in Image if required, the code creates an image internally anyways so I do not see any drawbacks here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's why I think the most suitable place for "good" scaling would be the Image class ;-)

Adding something to ImageData just because there is one consumer using it does not seem a good reason.

My point was obviously a bit inconclusive: the reason why all consumers use Image to scale ImageData is not because that's how to do that but it's because ImageData does not provide a proper way of doing so. Note that what the DPIUtil method for auto-scaling image currently does is not to scale an image but to scale ImageData.

I'm not entirely sure if the scale method in ImageData is a good idea at all and the best would probably to deprecate the method. Things like scaling and image are often better done at a dedicated place, as the code for doing so does not need access to any internals that justify it being part of ImageData itself...

I have to admit that I don't understand that argument. From OO perspective, if I have some data class describing an image and I want to scale that data, why shouldn't that functionality be provided at that data object? Image is an entity wrapping an image representation with all it's metadata, OS-related information, auto-scaling capabilities etc. Everything related to processing those data should actually not be part of that class (but encapsulated in ImageLoader for the loading capabilities and ImageData for the data manipulation). ImageData is the pure data with the ability to scale according to a desired size (i.e., at a lower level of abstraction than the scaling according to zoom used by Image).

I fully agree that in the best case the current algorithm using an intermediate Image to scale the data based on OS capabilities will be replaced by a generic algorithm, but that's nothing that needs to be done now but could be done as a follow-up improvement.
And the scaledTo() method of course uses all kinds of internals of ImageData. It's just that those internals are not really internal as ImageData is a class without proper encapsulation (all fields being public mutable).

Do you mean using Display.getCurrent()?

Exactly.

So concrete proposal:

  1. Would it be possible to place the implementation in ImageData without any API changes? In that case I would be in favor of doing that (cc @HannesWell we have also already discussed moving this scaling functionality to ImageData).
  2. If not, we should probably place the functionality inside Image consistently across the operating systems, in the best case factoring out the common part of the implementation in some utility. And we should then adapt relevant consumers (like in JFace, which we need to adapt anyway).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the reason why all consumers use Image to scale ImageData is not because that's how to do that but it's because ImageData does not provide a proper way of doing so.

As one usually need a Image to do anything useful with an ImageData i don't think it is that people don't want to use an Image#scaleTo method .. there is just no one there :-)

I have to admit that I don't understand that argument. From OO perspective, if I have some data class describing an image and I want to scale that data, why shouldn't that functionality be provided at that data object?

Because scale is obviously not a trivial operation and there are different ways to scale an image. Just look at the wikipedia article that list numerous approaches. So whatever we implement here will be bad for someone, it will break someones expectation if we change it later on (either in terms of performance, behavior, memory consumption, usage of native handles).

Even worse, scaling does not scale the ImageData (like a transformation does) it returns a new instance so there is really no reason to do it at the data object itself as this is more a data-carrier of device independent properties than a real object with mutators. The same argument would hold true to save an image, but we have dedicated ImageLoader (sic!) for saving an ImageData.

If you look at the AWT counterpart BufferedImage it also does not contain such thing like scaling or saving, as all this is better handled externally.

It's just that those internals are not really internal as ImageData is a class without proper encapsulation

ImageData is just a data carrier and the javadoc also mention this. Due to historical and technical reasons SWT do not encapsulate all data well, this has to be best handled on a higher level.

I fully agree that in the best case the current algorithm using an intermediate Image to scale the data based on OS capabilities will be replaced by a generic algorithm, but that's nothing that needs to be done now but could be done as a follow-up improvement.

From my point of view, the "done as a follow-up improvement" is exactly what org.eclipse.swt.graphics.ImageData.scaledTo(int, int) is currently missing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As one usually need a Image to do anything useful with an ImageData i don't think it is that people don't want to use an Image#scaleTo method .. there is just no one there :-)

Image#getImageData(zoom) is already there and used for exactly this (and could also be used by the mentioned place in JFace). And unfortunately ImageDescriptors (in particular the composite ones) try to do many things with just ImageData that should better be done with an Image ;-)

One argument we missed so far, which definitely makes scaling a capability that needs to be done by Image, is the fact that Image knows about the image data source and can retrieve an image in an according scale potentially without any need to perform scaling (by just loading it in proper scale, rasterizing an SVG or the like). This is something that cannot be achieved with any implementation of scaling outside Image (be it the ImageData class, some utility or anything else).
Given that, I withdraw my previous argumentation and agree with the solution of placing the functionality in Image: the image can best decide how to provide a scaled version. ImageData#scaledTo may then really be deprecated referring to the proper way of scaling across an Image instance.

Thank you for the discussion leading to this insight.

So I guess we agree that the functionality should be moved to the Image class then?

Regarding the issue with other consumers like Cursor: this rather seems to be a flaw anyway

  1. With the current API, we may create an Image for the ImageData and use its scaling capabilities. Yes, that is additional effort with an "unnecessary" temporary OS handle, but it then uses the "uniform" way of scaling.
  2. Initializing cursors with ImageData is a bad idea anyway. The data passed to the cursors is usually taken from some source (like an image descriptor), which could be capable of providing properly scaled images (i.e., based on an according data source like a 100%/200% raster graphics file or an SVG) whereas currently only scaling the given image data (potentially having a bad resolution) is possible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image#getImageData(zoom) is already there and used for exactly this (and could also be used by the mentioned place in JFace).

I just wanted to mention that this method does only allow ratio-preserving scaling but not arbitrary one.

Assume you have a Window with a background image and you grow/shrink the window, then you maybe need to enlarge the one axis by a different factor than the other. Even though it is maybe not that common it is impossible to do so with zoom... but one could do it in two steps of course. Here as well, SVG might offer superior results if rendered directly into a stretched version instead of performing it on the pixel data. so an Image#getImageData(x,y) would still be valuable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's a current limitation of all Image API: it is specific to auto-scaling (thus only capable of accepting a zoom value) and not capable of scaling to a specific width/height. We also discussed having a basic scaling concept of just passing desired height/width when implementing SVG support, as it would allow to render SVGs in arbitrary required size (like could be useful when zooming in diagrams that contain SVG images). But that's an extension independent of this change.

This reverts commit 909a985.

# Conflicts:
#	bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java
# Conflicts:
#	bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java
@amartya4256 amartya4256 force-pushed the amartya4256/smooth_scaling_rounding_error_fix branch 2 times, most recently from f8fc354 to 76a8eb4 Compare April 1, 2025 09:13
This commit contributes to moving the logic for scaling the ImageData
from DPIUtil to Image for each platform and use their own paltform
specific implementations. The method gets rid of the methods specific to
scaling ImageData, which are autoScaleImageData, scaleImageData, autoScaleUp
and autoScaleDown from DPIUtil.

contributes to
eclipse-platform#62 and
eclipse-platform#127
@amartya4256 amartya4256 force-pushed the amartya4256/smooth_scaling_rounding_error_fix branch from 76a8eb4 to 15c999a Compare April 1, 2025 09:28
@fedejeanne
Copy link
Contributor

@HeikoKlare , @laeubi are all your changes/suggestions met? Would you like to have this merged or would you rather wait for M2?

@amartya4256 amartya4256 changed the title Image Smooth Scaling rounding error fix Move ImageData Scaling Impl to Image for all platforms Apr 1, 2025
Copy link
Contributor

@HeikoKlare HeikoKlare left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The smooth scaling implementation is now replicated three times (in all Image implementations). Would it be possible to keep the overall algorithm in DPIUtil and just pass necessary parameterization/callback functionality to that method?

}

private ImageData scaleUsingSmoothScaling(ImageData imageData, int width, int height) {
Image original = new Image (device, (ImageDataProvider) zoom -> imageData);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we create a new image here and not using this Image?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't that create an endless loop? When you try to create image data at a specific zoom by painting the image into a GC, which requires requesting the image data at that specific zoom, this will likely produce a stack overflow.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea was that at the Image level we can access any internal API as required (including a painting the current ImageData instead of using ImageDataProvider).

return scaledTo (imageData, zoom, 100, DPIUtil.getScalingType(imageData));
}

private ImageData scaledTo(ImageData imageData, int targetZoom, int currentZoom, int scaleType) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private ImageData scaledTo(ImageData imageData, int targetZoom, int currentZoom, int scaleType) {
private Image scaledTo(int targetZoom, int scaleType) {

so the caller can operate on the Image (dispose it or use it or ...) also current zoom seems always 100, the image data can also better be fetched by the method itself

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This always being 100 is a specifics of the MacOS implementation. Independent from that making a scaledTo method return an Image would not really make sense, as there is no image being scaled (from which you could retrieve the data scaled according to different zooms again), but the data itself is scaled.

@laeubi
Copy link
Contributor

laeubi commented Apr 3, 2025

Would it be possible to keep the overall algorithm in DPIUtil and just pass necessary parameterization/callback functionality to that method?

I think it would been better to have an (package private) AbstractImage in the commons package.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants