On Thu, 4 May 2023 10:14:10 GMT, Martin Desruisseaux <d...@openjdk.org> wrote:

> The `BufferedImage` Javadoc does not mention any constraint about the data 
> type. In practice, `BufferedImage` with floating point values can be rendered 
> by Java2D as well as integers, provided that a compatible `ColorModel` was 
> supplied at construction time. However calls to `setData(Raster)` 
> unexpectedly cast floating point values to integers. For example sample value 
> 0.8 become 0. This is demonstrated by the `SetData` test case in this pull 
> request.
> 
> An easy fix, which is proposed in this pull request, is to replace the whole 
> `BufferedImage.setData(Raster)` method body by a simple call to 
> `WritableRaster.setRect(Raster)`, which handles all `DataBuffer` types 
> correctly. The method contracts documented in their respective Javadoc are 
> compatible. Furthermore an examination of their source code shows that they 
> are equivalent, except for the differences noted in the _Behavioural changes_ 
> section below.
> 
> # Source code comparison
> For demonstrating that delegating to `WritableRaster.setRect(Raster)` would 
> be equivalent to the current code, one can copy the method body and apply the 
> following changes:
> 
> * Remove `dx` parameter, replaced by 0.
> * Remove `dy` parameter, replaced by 0.
> * Rename `srcRaster` parameter as `r` (like in `BufferedImage`).
> * Rename `startY` variable as `i` (like in `BufferedImage`).
> * Rename `srcOffX` variable as `startX` (like in `BufferedImage`).
> * Rename `srcOffY` variable as `startY` (like in `BufferedImage`).
> * Replace `this.minX` by 0 and simplify.
> * Replace `this.minY` by 0 and simplify.
> * Remove the `switch` statement, keeping only the `TYPE_INT` case.
> 
> Then compare with `BufferedImage.setData(Raster)`. The modified block of code 
> from `WritableRaster` is:
> 
> 
> int width  = r.getWidth();
> int height = r.getHeight();
> int startX = r.getMinX();
> int startY = r.getMinY();
> 
> We can see that above code is identical to `BufferedImage`. The next modified 
> block of code from `WritableRaster`:
> 
> 
> int dstOffX = startX;
> int dstOffY = startY;
> 
> // Clip to this raster
> if (dstOffX < 0) {
>     width += dstOffX;
>     startX -= dstOffX;    // = 0 because dstOffX == startX
>     dstOffX = 0;
> }
> if (dstOffY < 0) {
>     height += dstOffY;
>     startY -= dstOffY;    // = 0 because dstOffY == startY
>     dstOffY = 0;
> }
> if (dstOffX+width > this.width) {
>     width = this.width - dstOffX;
> }
> if (dstOffY+height > this.height) {
>     height = this.height - dstOffY;
> }
> if (width <= 0 || height <= 0) {
>     return;
> }
> 
> 
> Above is equivalent to the following code from `BufferedImage`, ex...

A comment for keeping this merge request alive. Reminder: it is about resolving 
data lost in calls to `BufferedImage.setData(Raster)` caused by unnecessary 
casts of sample values.

Ping for keeping this pull request alive. Can we have a reviewer for it, or 
open a discussion about whether to expand the issue scope as mentioned in the 
description?

Issue reminder: `BufferedImage.setData(Raster)` lost data when using floating 
point values, without anything in the Javadoc suggesting that it is the 
expected behaviour. The proposed fix would resolve the data lost, *simplify* 
the code, possibly improve performances (but it is not the main goal) and be 
closer to what javadoc suggests.

Open question: current pull request does the smallest amount of changes, but 
more simplifications are possible. Should we add them to this pull request?

Thanks. Regarding the second issue (`getData()` and its derivatives), a closer 
look shows that I was wrong to said that it suffers the same problem than 
`setData(Raster)`. Only the latter needs to be fixed, so the current request 
should be sufficient.

Regarding the second open question about replacing current handcrafter 
intersection code by a call to `Rectangle.intersection(Rectangle)`, the 
following code:


        if (dstOffX < this.minX) {
            int skipX = this.minX - dstOffX;
            width -= skipX;
            srcOffX += skipX;
            dstOffX = this.minX;
        }
        if (dstOffY < this.minY) {
            int skipY = this.minY - dstOffY;
            height -= skipY;
            srcOffY += skipY;
            dstOffY = this.minY;
        }
        if (dstOffX+width > this.minX+this.width) {
            width = this.minX + this.width - dstOffX;
        }
        if (dstOffY+height > this.minY+this.height) {
            height = this.minY + this.height - dstOffY;
        }
        if (width <= 0 || height <= 0) {
            return;
        }


would be replaced by the following (this code contains a call to `getBounds()` 
overrideable method):


        Rectangle dstRect = new Rectangle(dstOffX, dstOffY, width, height);
        dstRect = dstRect.intersection(getBounds());
        if (dstRect.isEmpty()) {
            return;
        }
        srcOffX += dstRect.x - dstOffX;
        srcOffY += dstRect.y - dstOffY;
        dstOffX  = dstRect.x;
        dstOffY  = dstRect.y;
        width    = dstRect.width;
        height   = dstRect.height;


Would it be a worthly change?

I do not see variant of `Rectangle.intersection(…)` with `int` arguments [in 
the 
Javadoc](https://docs.oracle.com/en/java/javase/21/docs/api/java.desktop/java/awt/Rectangle.html).
 Maybe HotSpot can avoid the allocation with escape analysis. But anyway, the 
cost of `Rectangle` allocation is probably small comparatively to the cost of 
copying some thousands of pixel values.

Added a commit doing the replacement of handcrafter intersection in 
`WritableRaster.setRect(…)` by a call to `Rectangle.intersection(…)`. I 
couldn't run the jtreg tests however. I tried for a few hours, but I presume 
that I didn't configured correctly.

Note that `setRect(…)` is overridden in `sun.awt.image.ByteInterleavedRaster` 
and `sun.awt.image.BytePackedRaster` for optimization purposes. Those methods 
use the same handcrafter intersection code than the one replaced by this 
commit. However I didn't updated the code in those subclasses. They should not 
be subject to underflow / overflow because users should not extend these 
classes and thus access to the protected fields. We could update those methods 
anyway for consistency, but I do not know what is the OpenJDK policy in that 
case.

Regarding `BufferedImage.getData()`, I remember the issue. That method does not 
have the lost of precision issue documented in this pull request. But it would 
nevertheless benefit from the same change (delegate to 
`WritableRaster.setRect(…)`) for performance reasons, because of optimizations 
in above-cited `ByteInterleavedRaster` and `BytePackedRaster` subclasses.

-------------

PR Comment: https://git.openjdk.org/jdk/pull/13797#issuecomment-1607179108
PR Comment: https://git.openjdk.org/jdk/pull/13797#issuecomment-1685255511
PR Comment: https://git.openjdk.org/jdk/pull/13797#issuecomment-1798294061
PR Comment: https://git.openjdk.org/jdk/pull/13797#issuecomment-1800282141
PR Comment: https://git.openjdk.org/jdk/pull/13797#issuecomment-1808314079

Reply via email to