After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 549999 - scale: nearest neighbour, linear, cubic and lanczos have incorrect behavior in gegl svn 2581
scale: nearest neighbour, linear, cubic and lanczos have incorrect behavior i...
Status: RESOLVED OBSOLETE
Product: GEGL
Classification: Other
Component: operations
git master
Other All
: Normal normal
: ---
Assigned To: Default Gegl Component Owner
Default Gegl Component Owner
Depends on:
Blocks:
 
 
Reported: 2008-08-31 00:23 UTC by Nicolas Robidoux
Modified: 2012-01-22 18:02 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
Hack partially removing transparent border around outputted images (1.12 KB, application/x-gzip)
2009-08-13 04:36 UTC, Eric Daoust
Details
Hack partially removing transparent border around outputted images (2.08 KB, application/x-gzip)
2009-08-13 19:50 UTC, Eric Daoust
Details

Description Nicolas Robidoux 2008-08-31 00:23:18 UTC
Please describe the problem:

When upscaling---I have not checked downscaling---all the scaling methods available give unwanted behaviour.

Specifically:

a) When used with the same scales, the four methods give images with different
dimensions (no two method give the same size for the same image and scaling factors).

Note that I understand that this may be a good thing: For example, nearest neighbour is more compatible with the "center" image size convention, and linear is more compatible with the "corner" image size convention. However, 
the fact that each and every method produces its own dimensions is very disorienting.

I suspect that there is something fishy in the way the output image dimensions are computed from the scales? I could not figure out where this is done in the code.

b) Every method produces transparent pixels at the boundary, with the thickness of this incorrect boundary different for each method. Nearest and linear only do so at the bottom and right boundaries, cubic and lanczos at all four.

This may be related to the current simple minded abyss policy, but if the policy is not changed (to smear, say), the code for the methods need to be changed, or the sampled pixels need to be set so that they are far enough away from the boundary that the abyss is not used in the computation (quick and dirty fix, which has the unfortunate side effect of cropping the image slightly for cubic and lanczos, and still requires careful handling of the bottom and right boundaries for nearest and linear).

This is related to the "area-sampler" border pixel values issue.



Steps to reproduce:

I do not know if this bug exists in earlier versions of gegl, so you need to have a recent svn version installed (I used 2581, the current one as of today).

http://web.cs.laurentian.ca/robidoux/misc/

constains a gzipped tar file called gallery.tgz

Download it.

In the following, "..." refers to the folder in which the gegl source is located. I assume that you have rwx permission to this folder. If no, su or sudo, and chmod if needed.

Basically, you will replace the "stock" gallery folder by one which exhibits the problem (you may, of course, mv the stock gallery folder elsewhere, so you 
can copy it back later).

cd .../gegl/docs
rm -rf gallery
cp where_ever_you_downloaded_it_into/gallery.tgz .
tar xvzf gallery.tgz
cd gallery
make clean
make

The test images are courtesy of the excellent

http://www.cambridgeincolour.com/tutorials/digital-photo-enlargement.htm

Now:

If you point your browser to

file:///.../gegl/docs/gallery/index.html

you will see the page generated by the various xml files, which differ only
in the input image used (flag or cat, found in the data subfolter) and the method used to upscale (nearest, linear, cubic or lanczos).

In order to easily view the enlargement's dimensions, and see the erroneous transparent borders, do this:

cd .../gegl/docs/gallery
gimp *.png

This should make the above assertions obvious.

Actual results:
The most likely incorrectly method dependent distinct output image dimensions, as
well as the incorrect transparent borders, are seen.

Expected results:
Image dimensions which are more "consistent" across methods ("center" or "corner" or both, but not the current hard to figure one).

No transparent borders.

Does this happen every time?
Yes.

Other information:
If someone can point out to me where the numbers of pixels of the enlargements, as well as the coordinates (relative to the buffer's origin, which is, I understand, top/left=(0,0)) of the sampled pixels, I may be able to fix the bug.

Apologies if this bug report is redundant.

"Nicolas Robidoux" <nrobidoux@cs.laurentian.ca>
Comment 1 Øyvind Kolås (pippin) 2008-08-31 11:29:35 UTC
The bug is manifest because the different interpolation methods have different sampling footprints. The resulting image size is computed based on the size of the kernel, this is similar in behaviour to the gaussian blur which increases the size of the input image as well by smearing it out. The upper left corner of the bounding box is stored to be in negative coordinats so sampling from 0,0 would yield the upper left sample anyways. One way to work around this would be to crop the image to the original bounding box.

Not completely sure if this is a bug or not, I guess it depends on what types of behavior we want to support for the edges of transformed buffers.
Comment 2 geert jordaens 2008-09-14 14:34:20 UTC
I noticed this also when working on the samplers and indeed it has to do with the width of the interpolation functions.

When scaling (affine op)

The get_required_for_output/get_bounding_box and get_invalidated_by_change are extended by the context_rect of the sampler. In the case of a real edge (not a tile edge) the pixels could depending on the edge strategy be copied/initialized or mirrored. This prevents the need of handling the edge situation in the sampler.

However I think meanwhile the edge conditions are handled by using gegl_sampler_get_ptr.
Comment 3 Nicolas Robidoux 2008-09-14 14:59:25 UTC
IMHO, I think that boundaries should be handled inside the samplers, not with abyss policies which cannot be made to do the right thing for all samplers/operations.

This is not as much of a big deal as it seems:

The operation/sampler designer is the most qualified to know what to do with "missing data"/"computed values for points outside the image/region."

The information which is needed is mostly passed to the gegl-sampler-***.c already:

We already know that the left top pixel is at 0, 0.

In order to know where the boundary is all around, all that is needed is the 
pixel width and pixel height of the input image.

If this info was passed, I could immediately fix yafr so that it never tries
to access information beyond the boundary (that is, the answer would be independent of the abyss policy), so that the pixel values near boundary "are as accurate/good looking" as possible, AND so that if the sampler is asked
for pixel values at points outside of [0,width-1]x[0xheight-1] (for example, if 
rotation is performed), then the values which are extrapolated as opposed to interpolated are reasonable (in the case of yafr, I'd fix them so that the transition is continuous from inside to outside, and so that values are constant
when one goes "straight out" of the original area, which if you think about it, is pretty much the most reasonable choice).

-------

There are additional possible benefit to passing this information:

If one wants to resample then crop, this could be done in one merged operation
which uses all the appropriate pixel values, but does not compute pixel values which would be cropped. Actually, it would be easy to specify the exact locations of the corner pixels of the resampled area, making extend beyond the original image area even if one is upsampling or downsampling, without ill effect.

This would allow easy switching between "corner" image size conventions (good for interpolatory methods, like nearest neighour, bilinear, cubic, lanczos and yafr) and "center" conventions (good for exact area methods, like box filtering, and Exact Area Natural BiQuadratic Histosplines, for which a plug-in will appear for Gimp classic soon).

I feel pretty strongly about this:

If this could be implemented into GEGL, some of my medical imaging friends would
use it. Without tight control of which image size convention is used (they need "center"), they won't, and I'll have to produce code versions just for their purpose.

The other thing I would like is the possibility to precisely control the numbers of pixels in the resampled image, as opposed to trusting the rounding behavior of the vague "scale."

-----------

It is possible that I can get what I'd like by picking it out of the already existing objects, but it would be nice if I could get it in a transparent way.

Nicolas Robidoux
Laurentian University/Universite Laurentienne

Nicolas Robidoux
Laurentian University/Universite Laurentienne  
Comment 4 Nicolas Robidoux 2009-02-26 17:58:32 UTC
I am not sure I stand by my previous solutions anymore.

In GPUs, this is often handled by "clamping" coordinates during input pixel lookup. 

VIPS has a solution to this problem---based on somehow extending the input image---which is consistent with demand-driven and does not affect performance.

In summary:

I'm not sure how this should be fixed. I just think that it should be fixed.

Nicolas Robidoux
Universite Laurentienne
Comment 5 Nicolas Robidoux 2009-07-28 14:58:59 UTC
Could I get some feedback on the following?

Looking at the way Adam Turcotte is trying to adaptively change the footprint of the (GSoC new) downsampling samplers, it occured to me that a hack which would
most likely take care of the sampler dependent border issue would be, for example, to change

gegl_sampler_cubic_init (GeglSamplerCubic *self)
{
 GEGL_SAMPLER (self)->context_rect.x = -1;
 GEGL_SAMPLER (self)->context_rect.y = -1;
 GEGL_SAMPLER (self)->context_rect.width = 4;
 GEGL_SAMPLER (self)->context_rect.height = 4;

to

gegl_sampler_cubic_init (GeglSamplerCubic *self)
{
 GEGL_SAMPLER (self)->context_rect.x = 0;
 GEGL_SAMPLER (self)->context_rect.y = 0;
 GEGL_SAMPLER (self)->context_rect.width = 0; /* or 1? */
 GEGL_SAMPLER (self)->context_rect.height = 4; /* or 1? */

and then add the following lines

 GEGL_SAMPLER (self)->context_rect.x = -1;
 GEGL_SAMPLER (self)->context_rect.y = -1;
 GEGL_SAMPLER (self)->context_rect.width = 4;
 GEGL_SAMPLER (self)->context_rect.height = 4;

to the top of

gegl_sampler_cubic_get (GeglSampler *self,
                        gdouble      x,
                        gdouble      y,
                        void        *output)

I have not looked at things carefully.

I have the impression that the extra borders may be so that operations can safely be chained: This way, whatever previous node is feeding data to the sampler is made aware that a larger area is needed. So, I am a bit concerned that the above hackish solution may break chains of operations.

A less hackish, more much more labor intensive, would consist of separating more clearly the footprint from the computation bounding box (I may have the wrong terminology here: no time to look at the source code just now). But, again, if "enlarged" bounding boxes are needed in order to move information upstream about needed pixel values, this may break things.

Nicolas Robidoux
Universite Laurentienne
Comment 6 Nicolas Robidoux 2009-07-28 15:23:45 UTC
Typo in my last post:

 GEGL_SAMPLER (self)->context_rect.width = 0; /* or 1? */
 GEGL_SAMPLER (self)->context_rect.height = 4; /* or 1? */

should have been

 GEGL_SAMPLER (self)->context_rect.width = 0; /* or 1? */
 GEGL_SAMPLER (self)->context_rect.height = 0; /* or 1? */

Apologies.
Comment 7 Eric Daoust 2009-08-13 04:36:41 UTC
Created attachment 140621 [details]
Hack partially removing transparent border around outputted images

Applies to Linear and Cubic samplers.

Initialize values to:
 GEGL_SAMPLER (self)->context_rect.x = 0;
 GEGL_SAMPLER (self)->context_rect.y = 0;
 GEGL_SAMPLER (self)->context_rect.width = 1;
 GEGL_SAMPLER (self)->context_rect.height = 1;

Then set the context rect to sampler-specific values when calling "get".
Comment 8 Eric Daoust 2009-08-13 19:50:14 UTC
Created attachment 140697 [details]
Hack partially removing transparent border around outputted images

Removed unnecessary assigning of context_rect values in linear sampler, also cosmetic changes fixing some text alignment.
Comment 9 Nicolas Robidoux 2009-08-20 01:47:51 UTC
This hacks succeeds in reducing the size of the extra (partially) transparent border when resizing and resampling, and apparently does not introduce other problems. Unfortunately, it does not remove it completely for methods with large footprint (stencil), because the transparent black abyss creeps in.

IMHO, the abyss/implied image size issues will require a good amount of careful code rewrite, so this may be a good temporary measure. On the other hand, keeping the transparent border there may make it clear that something drastic needs to be done (instead of "hiding most of it away").

In other words: This is ready to commit if you are OK with this hack.

Nicolas Robidoux
Laurentian University
Comment 10 Eric Daoust 2009-08-20 17:22:52 UTC
I have investigated the default abyss color problem, and it was determined to be too time consuming for GSoC and would require a large code change at various levels throughout GEGL because this would require passing parameters from the xml test file to the operation, which is different from passing parameters to the samplers.

In affine.c, I found a line of code that is suspicious.  I do not know exactly what it does, but it seems that it may be the line that passes information to buffer-access (where abyss stuff is done):

      output = g_object_new (GEGL_TYPE_BUFFER,
                             "source",    input,
                             "shift-x",   (int)-affine->matrix[0][2],
                             "shift-y",   (int)-affine->matrix[1][2],
                             "abyss-width", -1,  /* turn of abyss
                                                    (relying on abyss
                                                    of source) */
                         NULL);
Comment 11 Nicolas Robidoux 2009-08-29 16:13:41 UTC
(In reply to comment #10)
>       output = g_object_new (GEGL_TYPE_BUFFER,
>                              "source",    input,
>                              "shift-x",   (int)-affine->matrix[0][2],
>                              "shift-y",   (int)-affine->matrix[1][2],
>                              "abyss-width", -1,  /* turn of abyss
>                                                     (relying on abyss
>                                                     of source) */
>                          NULL);

For the record, when using homogeneous coordinates, and when matrix[2][2]=1
(which is always the case for affine operations in gegl?), matrix[0][2] is
the x-translation component of the transformation, and matrix[1][2] is the 
y-translation component.

Nicolas Robidoux
Universite Laurentienne
Comment 12 Jon Nordby 2011-09-03 18:05:35 UTC
Does this issue still exist?
Comment 13 Tobias Mueller 2012-01-22 13:36:17 UTC
Nicolas, can you answer the question in comment #12?
Comment 14 Nicolas Robidoux 2012-01-22 15:02:47 UTC
All this code has changed so much that the best thing to do is to mark as RESOLVED. If I find it is still a problem when I get back into GEGL programming, I'll open a new bug report with specific references to the current code.